Faid
Faid

Reputation: 605

Import a Vue js component from Laravel Blade File

I have registered a some components as a global components in js/app.js file, But this makes the compiled app.js file larger.

//example: app.js
Vue.component('profile-page', require('./components/profiles/ProfilePage.vue').default);

The question is: Is there a way to import any of these global component in the laravel-blade file instead of registering it globally in app.js file?

something like this:

// laravel-blade file
<script>
    import ProfilePage from ...;
</script>

Upvotes: 3

Views: 8840

Answers (4)

nekooee
nekooee

Reputation: 315

You can load components as lazyload. This method is better than creating multiple js files. It is not loaded until the component is used.

for Vue2:

const myComponent = () => import('./Components/MyComponent')
Vue.use(MyComponent)

for Vue 3:

import { defineAsyncComponent } from 'vue'
const MyComponent = defineAsyncComponent(() =>
  import('./components/LoginPopup.vue')
)
..
...
 components: {MyComponent}

Upvotes: 0

Clean Code Studio
Clean Code Studio

Reputation: 699

To further extend on Salim Example, you can add Vue to the window and directly create the exported Vue Component within the Vue File.



1) Autload Vue Within Laravel Mix


webpack.mix.js

const mix = require('laravel-mix');

mix.autoload({vue: ['Vue', 'window.Vue']})
   .js(...)
   .css(...)
   .version()

2) Register Vue Components Globally As You Create Them


resources/js/components/profile/profile-image.vue

<template>
    <div class='profile-image' @click='show(user)'>
        <img :src='user.avatar' :alt='`${user.name} profile image`' />
    </div>
</template>

<script>
  /** Note: Global Component Registered Via Vue.component(...) **/

  Vue.component('profile-image', {   
     props: ['user'],

     methods: {
        /** 
         * Show User Profile Page
         */
         show(user) {
            const { location } = window;

            window.location = `${location.origin}/users/${user.id}`;
         }
      }
   });
</script>

3) Instead of requiring each component simply use Laravel Mix

webpack.mix.js

const mix = require('laravel-mix');

mix.autoload({ 
  vue: [
     'Vue', 
     'window.Vue'
  ] 
})
.js([
      /* --------------------------------- 
       |   Card Components
       | ---------------------------------
       |
       | . Card.vue (Original)
       | . IconCard.vue (Topic Contextually Relevant Icon)
       | . DetailCard.vue (Shown On Detail Pages & Used To Summarize Index Tables)
       |
      */
      'resources/js/components/cards/card.vue',
      'resources/js/components/cards/icon-card.vue',
      'resources/js/components/cards/index-card.vue',
      'resources/js/components/cards/detail-card.vue',
      'resources/js/components/cards/organization-card.vue',

      /* --------------------------------- 
       |   Button Components
       | ---------------------------------
       |
       | . Button.vue (Original)
       | . ButtonRipple.vue (Interactive Click Effects)
       | . ButtonFabIcon.vue (Rounded, Material Design Icons)
       |
      */
      'resources/js/components/buttons/button.vue',
      'resources/js/components/buttons/primary.vue',
      'resources/js/components/buttons/success.vue',
      'resources/js/components/buttons/button-ripple.vue',
      'resources/js/components/buttons/primary-ripple.vue',
      'resources/js/components/buttons/success-ripple.vue',
      'resources/js/components/buttons/button-fab-icon.vue',
      'resources/js/components/buttons/primary-fab-icon.vue',
      'resources/js/components/buttons/success-fab-icon.vue',



      /* --------------------------------- 
       |   Fields Components
       | ---------------------------------
       |
       | . Form.vue (Create & Update)
       | . Detail.vue (Show, Edit, & Cards)
       | . Index.vue (Tables Ex: Sort, Search, Filter)
       |
      */
      'resources/js/components/fields/date/form.vue',
      'resources/js/components/fields/date/index.vue',
      'resources/js/components/fields/date/detail.vue',

      /** Then that one component we actually created ;D **/
      'resources/js/components/profile/profile-image.vue',

], 'resources/js/components/bootstrap.js')


.babel([
      /* ------------------------------------------------------------------
       | Mounting Vue & Using "Babel" (Vanilla JS For Every Browsers)  
       | ------------------------------------------------------------------
       |
       | . Our Components are compiled
       | . Our Last File Being Added Will Mount Vue
       | . We'll Use ".babel()" While Adding This File
       | . "Babel" Simply Transforms All Javascript Into Vanilla JS
       |
      */
        'resources/js/components/bootstrap.js', 
        'resources/js/bootstrap/mount-vue.js'

], 'public/js/app.js')


/*------------------------------*/
/* Optimization Minification   
/*------------------------------*/
.minify('public/js/app.js');

/*------------------------------*/
/* Cache Busting Versioning   
/*------------------------------*/
if (mix.inProduction()) {
  mix.version();
}

4) Simplify Further By Extending Laravel Mix

resources/js/mix-extensions/mix-every-vue-component.js

import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // The relative path of the components folder
  './components',
  // Whether or not to look in subfolders
  false,
  // The regular expression used to match base component filenames
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // Get component config
  const componentConfig = requireComponent(fileName)

  // Get PascalCase name of component
  const componentName = upperFirst(
    camelCase(
      // Gets the file name regardless of folder depth
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )


  // Register component globally
  Vue.component(
    componentName,
    // Look for the component options on `.default`, which will
    // exist if the component was exported with `export default`,
    // otherwise fall back to module's root.
    componentConfig.default || componentConfig
  )
})

webpack.mix.js

const mix = require('laravel-mix');

class LaravelMixEveryVueComponent
{
    public constructor() {

    }

}
mix.autoload({ 
  vue: [
     'Vue', 
     'window.Vue'
  ] 
})
.js([
      /* --------------------------------- 
       |   Card Components
       | ---------------------------------
       |
       | . Card.vue (Original)
       | . IconCard.vue (Topic Contextually Relevant Icon)
       | . DetailCard.vue (Shown On Detail Pages & Used To Summarize Index Tables)
       |
      */
      'resources/js/components/cards/card.vue',
      'resources/js/components/cards/icon-card.vue',
      'resources/js/components/cards/index-card.vue',
      'resources/js/components/cards/detail-card.vue',
      'resources/js/components/cards/organization-card.vue',

      /* --------------------------------- 
       |   Button Components
       | ---------------------------------
       |
       | . Button.vue (Original)
       | . ButtonRipple.vue (Interactive Click Effects)
       | . ButtonFabIcon.vue (Rounded, Material Design Icons)
       |
      */
      'resources/js/components/buttons/button.vue',
      'resources/js/components/buttons/primary.vue',
      'resources/js/components/buttons/success.vue',
      'resources/js/components/buttons/button-ripple.vue',
      'resources/js/components/buttons/primary-ripple.vue',
      'resources/js/components/buttons/success-ripple.vue',
      'resources/js/components/buttons/button-fab-icon.vue',
      'resources/js/components/buttons/primary-fab-icon.vue',
      'resources/js/components/buttons/success-fab-icon.vue',



      /* --------------------------------- 
       |   Fields Components
       | ---------------------------------
       |
       | . Form.vue (Create & Update)
       | . Detail.vue (Show, Edit, & Cards)
       | . Index.vue (Tables Ex: Sort, Search, Filter)
       |
      */
      'resources/js/components/fields/date/form.vue',
      'resources/js/components/fields/date/index.vue',
      'resources/js/components/fields/date/detail.vue',

      /** Then that one component we actually created ;D **/
      'resources/js/components/profile/profile-image.vue',

], 'resources/js/components/bootstrap.js')


.babel([
      /* ------------------------------------------------------------------
       | Mounting Vue & Using "Babel" (Vanilla JS For Every Browsers)  
       | ------------------------------------------------------------------
       |
       | . Our Components are compiled
       | . Our Last File Being Added Will Mount Vue
       | . We'll Use ".babel()" While Adding This File
       | . "Babel" Simply Transforms All Javascript Into Vanilla JS
       |
      */
        'resources/js/components/bootstrap.js', 
        'resources/js/bootstrap/mount-vue.js'

], 'public/js/app.js')


/*------------------------------*/
/* Optimization Minification   
/*------------------------------*/
.minify('public/js/app.js');

/*------------------------------*/
/* Cache Busting Versioning   
/*------------------------------*/
if (mix.inProduction()) {
  mix.version();
}

4. Extend Laravel Mix Removing All Extra Steps

laravel-mix-autoload-vuejs-extension.js

const mix = require('laravel-mix');


const CollectFiles = (folder, files = []) => {
    const isFolder = to => File(path.resolve(to)).isDirectory();
    const CombineFiles = (Files, Segments = []) => [ ...files, path.join(__dirname, Segments[0], '/', Segments[1])];

    return fs.readdirSync(folder).reduce((filed, file) =>
            isFolder(`${folder}/${file}`)
                ? CollectFiles(`${folder}/${file}`, files)
                : CombineFiles(files, [folder, file]),
        files
    ).map(string => string.replace(__dirname, ''));
};


class LaravelMixAutoloadVue
{
    constructor()
    {
        this.LoadVueComponents = (to, output) => mix.js(CollectFiles(to), output);

        return mix;
    }

    dependencies()
    {
        return ['fs', 'path'];
    }

    name()
    {
        return ['vuejs'];
    }

    register(to, output)
    {
        if (typeof to === 'undefined') {
            return console.log(`Output is undefined for codesplit path ${to}`);
        }

        this.LoadVueComponents(to, output);
    }

    boot()
    {
        console.log("Booting Example");
    }
}

mix.extend('vuejs', new LaravelMixAutoloadVue());

webpack.mix.js webpack.mix.js

const mix = require('laravel-mix');
require('./laravel-mix-autoload-vuejs`);

mix.autoload({ 
  vue: [
     'Vue', 
     'window.Vue'
  ] 
})
      /* -------------------------------------------------------------
       |  Laravel Mix Autoload Vue Extensions Handles All Components
       | -------------------------------------------------------------
      */
.vuejs('resources/js/components/', 'resources/js/components/bootstrap.js') 
.babel([
      /* ------------------------------------------------------------------
       | Mounting Vue & Using "Babel" (Vanilla JS For Every Browsers)  
       | ------------------------------------------------------------------
       |
       | . Our Components are compiled
       | . Our Last File Being Added Will Mount Vue
       | . We'll Use ".babel()" While Adding This File
       | . "Babel" Simply Transforms All Javascript Into Vanilla JS
       |
      */
        'resources/js/components/bootstrap.js', 
        'resources/js/bootstrap/mount-vue.js'

], 'public/js/app.js')


/*------------------------------*/
/* Optimization Minification   
/*------------------------------*/
.minify('public/js/app.js');

/*------------------------------*/
/* Cache Busting Versioning   
/*------------------------------*/
if (mix.inProduction()) {
  mix.version();
}

Upvotes: 0

Mohamed Allal
Mohamed Allal

Reputation: 20840

As like Caddy DZ answered it's the way to go with laravel-mix. (CaddyDz happen to be my close friend by the way hhhh)

https://stackoverflow.com/a/58122158/7668448

Multi pages, multi bundles! laravel-mix-glob is the answer

However if there is multi pages. And keeping doing it that way. It's a bit of hassle. Or not the coolest of the ways. For this purpose i developed a pakage laravel-mix-glob. Which is a wrapper around laravel-mix. That do some magic for you.

Which allow you to use globs and have all the files that get added automatically handled for you. In place of managing them file by file. Page by page.

The use is so simple. You can check the package here:

https://www.npmjs.com/package/laravel-mix-glob

The documentation explain everything. You have to check the portion about the compileSpecifier

You can give it a one read. And then you'll be more productive. And the magic just happen. Even everything is explained within the doc even how laravel-mix-glob work.

You can also check this issue. Which show some nice points:

https://github.com/MohamedLamineAllal/laravel-mix-glob/issues/5#issuecomment-537991979

And to clear things even just here. Here a use example:

// imports
const mix = require('laravel-mix'); // you need the laravel mix instance
const MixGlob = require('laravel-mix-glob');

// init
const mixGlob = new MixGlob({mix}); // mix is required
// or 
const mixGlob = new MixGlob({
    mix, // mix required
    mapping: { // optional
        // see the doc
    },
    // more options maybe added in future version (fill issues if you need anything, or a PR if you like)
});


// use mixGlob
mixGlob.sass('resources/sass/**/*.compile.scss', 'public/css', null, {
    base: 'resources/sass/',
    // compileSpecifier: { 
    //     disabled: true // there is no compile specifier (disabled), and so it will not be removed from the extension (by default disabled = false, and the default specifier = 'compile', and it get removed from the path)
    //      ,
    //      specifier: 'cmp'
    // }
    // mapping: {   // this take precedency over any other mapping // useless feature as laravel-mix doesn't support output in different formats. (until i find a workaround)
    //     ext: {
    //         'scss': 'css' // multiple files separatly
    //     },
        // or
        // ext: 'css', // all to the same
        //   
    // }
})
.js(['resources/js/**/*.compile.{js,jsm}', '!resources/js/secondPattern/**/*'], 'public/js/', null, {
    base: 'resources/js/'
}) // multiple globs pattern as an array. Also with exclusion support (!)
.js('resources/js/secondPattern/**/*.compile.{js,jsm}', 'public/js', null, {
    base: 'resources/js/secondPattern'
})
.ts(['resources/js/ts/**/*.compile.ts', 'resources/js/tsx/**/*.compile.tsx'], 'public/js', null, {
    base: {
        ts: 'resources/js/ts/', // per file extension  mapping
        tsx: 'resources/js/tsx/**/*.compile.tsx'
    }
})
.mix('sass')('resources/sass/summernote.scss', '../resources/views/system/admin/dashboard/partials/_summernote_css.blade.php'); // laravel-mix instance

Some notes

For the bellow

.js(['resources/js/**/*.compile.{js,jsm}', '!resources/js/secondPattern/**/*'], 'public/js/', null, {
    base: 'resources/js/'
})

It translate to take all the js or jsm files in the directory resources/js/ or any of it's sub directories at all levels. And that are not part of resources/js/secondPattern/**/*. And output them in public/js. Holding the same structure from the base resources/js/. Whenever you add a new file that respect that structure it will be automatically compiled for you (well laravel-mix watcher will be restarted and with it the whole build). And you don't have to do it file by file. At all.

For instance let say at start you have 6 files that match the patterns. laravel-mix-glob automatically will make all the 6 right calls. And then even when you add new file it know automatically and recompile.

And laravel-mix-glob leverage all the best glob patterns. In the most intuitive way. Going from simple to the most complex. And the people used to use the glob libraries. Gulp. Or many other tools. Will just find it too familiar. Everything is simple. And it's all explained in the doc. There is many examples too.

compileSpecifier

It's an important feature. Imaging you want to bundle only few files from many. Adding the specifier and having the feature to be automatically managed and stripped from the output is just interesting and effective. That's the motivation. By default is activated you can deactivated as shown in the example bellow.

Final words

Check the doc as it's more complete and tackle all the different parts. The package was been there for months now. And it was well tested in Linux. More less in windows. But many users of both platform used it. And it work perfectly and magically. To give you more comfort and allow you to be more productive.

Also as the author i'm too open to the community. I review and handle PR with great joy. And i like to have contributors. So any interested one can let me know. Here or by filling an issue.

Upvotes: 1

Salim Djerbouh
Salim Djerbouh

Reputation: 11034

register the component in another file rather than app.js

resources/js/example.js

window.Vue = require('vue');
Vue.component('example-component', require('./components/ExampleComponent.vue').default);

Compile your component to another file in webpack.mix.js

mix.js('resources/js/app.js', 'public/js')
   .js('resources/js/example.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

Include it in blade

<script src="/js/example.js"></script>

Upvotes: 7

Related Questions