Laravel-mix: Vue.js Async Components

Created on 8 Oct 2017  路  33Comments  路  Source: JeffreyWay/laravel-mix

  • Laravel Mix Version: 1.4.5
  • Node Version (node -v): 8.5.0
  • NPM Version (npm -v): 5.4.2
  • OS: macOS High Sierra

Description:

Does anyone know how to get Vue.js async components to work? Getting the following error:

```error in ./resources/assets/js/app.js

Syntax Error: Unexpected token (10:25)

8 | el: '#app',
9 | components: {

10 | 'example': () => import('./components/Example')
| ^
11 | }
12 | });```

Most helpful comment

@ruchern
It is specific to webpack (tooling), after all it is babel who will transform the code and will pass to uglify-js. So it is upto end user to configure and enable modern feature.

@pix2D

Here is the complete tutorial -

鈿狅笍 Updated for Laravel Mix v3+

npm install --save-dev @babel/plugin-syntax-dynamic-import
  • Create .babelrc in your project root, and specify the plugin
{
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
  • Then in your code somewhere -
// resources/js/routes.js
{
          path: 'post/:id(\\d+)',
         // notice the dynamic import
          component: () => import('./components/post/show.vue'),
          name: 'post.show',
          meta: {
            title: 'Show post',
          }
 }
  • Build
npm run dev

PS
If you want to load components via computed property, read this

All 33 comments

components: {
    example: require('./components/Example')
}

@ruchern That's the normal way of pulling it in, I'm trying to do it asynchronously instead.

@ankurk91 Why is that not a Vuejs problem instead but a Laravel Mix one?

@ruchern
It is specific to webpack (tooling), after all it is babel who will transform the code and will pass to uglify-js. So it is upto end user to configure and enable modern feature.

@pix2D

Here is the complete tutorial -

鈿狅笍 Updated for Laravel Mix v3+

npm install --save-dev @babel/plugin-syntax-dynamic-import
  • Create .babelrc in your project root, and specify the plugin
{
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
  • Then in your code somewhere -
// resources/js/routes.js
{
          path: 'post/:id(\\d+)',
         // notice the dynamic import
          component: () => import('./components/post/show.vue'),
          name: 'post.show',
          meta: {
            title: 'Show post',
          }
 }
  • Build
npm run dev

PS
If you want to load components via computed property, read this

@ankurk91 Thank you, that works perfectly. Any idea how to make the files go to public/js instead of public?

@pix2D
See my last point. (not tested).
ref #1055, #936

@ankurk91 Thanks, that works great.

Anyone know how to get this working when programmatically mounting components afterwards?

@emielmolenaar
can you share an example on how you want it ?

@ankurk91 ,
Using dynamic import - all works, chunks loading. Thanks for your input!

I guess @emielmolenaar faces same issue i have..

There's an issue when i use dynamic computed component name using ().

return () => import(./${this.componentFile});
Can u assume why this generates 9 chunks? And when component conditionally loads - browser loads only one chunk of that 9.

Where to dig?

@ankurk91 We have implemented vue.js together with async components in a somewhat older app. The only legacy library that is left is datatables.net, which parses HTML tables by received JSON stuff from the server. The server generates some HTML for action buttons, which may contain stuff like

<a is="SomeComponent"></a>

We then use some hacks to mount the components after the DOM has changed:

function mountVueComponents(table) {
    let elements = Array.prototype.slice.call(table[0].querySelectorAll('td div[is]'), 0);

    if (elements.length > 0) {
        elements.forEach(function (element) {
            try {
                /**
                 * Hacky way to mount Vue components
                 */
                let component = Vue.component(element.getAttribute('is'));

                let args = {
                    propsData : {},
                    parent : window.vm
                };

                for (let i = 0; i < element.attributes.length; i++) {
                    let attrib = element.attributes[i];
                    if (attrib.specified && attrib.name != 'is') {
                        args.propsData[_.camelCase(attrib.name)] = attrib.value;
                    }
                }

                new component(args).$mount(element);
            } catch(error) {
                console.log(error);
            }
        });
    }
}

Until we have moved away from datatables.net, when have to work this way... This function works for now, but not with components that are loaded asynchronous. Any hint in the right direction? Thanks!

@emielmolenaar
I have not tried on-demand imports or import callback yet but i found some stuff, here are links-
http://2ality.com/2017/01/import-operator.html
https://github.com/tc39/proposal-dynamic-import
https://webpack.js.org/guides/code-splitting/#dynamic-imports

You should be able do something like this -

import('./myComponent.js')
.then(({default: theDefault}) => {
    console.log(theDefault);
   // do mount operation here
});

Any plans in the future to have the babel stuff included and pre-configured, or is this not a common enough thing that users ask for which means we should just configure it manually when we need it?

@raniesantos
PR welcome

@ankurk91
Sorry, but I have a problem about the import. It generates chunk js files correctly, but when it tries to download the js file, it uses relative paths instead of absolute paths. Therefore, it requests the wrong url and an error occurs.
For example, I have 0.js in my public folder. But when I visit dynamic page /ABC/DEF, it just requests /ABC/0.js and results in an error...
Thank you in advance for your help!

@turtlegood
Never faced this issue. Cant say anything now.

@ankurk91 Could you please paste the code of how you use it? I think maybe it is because of some code which you did not paste and I did wrong.

@ankurk91 My case:

    .webpackConfig({
        plugins: [
            new webpack.ProvidePlugin({
                jQuery: 'jquery',
                $: 'jquery',
                jquery: 'jquery',
                'window.jQuery': 'jquery',
            }),
            new webpack.ProvidePlugin({
                Promise: 'es6-promise-promise'
            })
        ],
    })
    .js('resources/assets/js/app.js', 'public/gen')
        .extract([
            'jquery',
            'vue',
            'vue-router',
            './resources/assets/js/libs/materialize.js',
            ......
        ])
        .version()
    .sass('resources/assets/sass/app.scss', 'public/gen')
    .styles([
        'resources/assets/sass/libs/materialize.css',
        ......
    ], 'public/gen/vendor.css')

Thank you very much in advance!

@turtlegood Please look at https://github.com/JeffreyWay/laravel-mix/issues/863#issuecomment-324332719 or https://github.com/JeffreyWay/laravel-mix/issues/863#issuecomment-366662394 Which provides a fix for the problem mentioned in the linked issue

@turtlegood
I have updated my code in above comment as well.
publicPath: '/',
was missing there.

@spaceemotion @ankurk91 Thanks!!!

Is it just me or after implementing this - the npm run watch stopped to work properly(doesn't seem to reflect the code changes)... Now you have to just run npm run dev for full change...

Edited :
Another disadvantage that I've noticed after implementing this is that my global components are no longer working - need to import them in the vue file....

This doesn't seem to be Laravel Mix issue. Same happens with fresh Vue cli generated codebase. Interesting is that it happens only with first async component. Compiles, runs and works perfectly tho.
image

Even after setting up syntax-dynamic-import this still gives me an error of:

Unexpected token import

I fail to see what is still going wrong especially when I already have a stage-2 preset set up, so I know that my .babelrc file is working perfectly fine.

Figured it out

Transpiling and parsing actually went fine it was the eslint configuration which was not set up for experimental features.

To support features like dynamic imports this babel-eslint parser needs to be set up:

https://github.com/babel/babel-eslint

Simply a matter of installing it:

babel-eslint@8 --save-dev

And updating your .eslintrc file:

"parser": "babel-eslint"

Hopefully this might help someone.

Hi guys, thanks for the nice feature of code splitting. Modules are now splitting. But the issue is with cache busting. The aproach with chunkFilename: 'js/[name].[chunkhash].js', generates new files each time.

@lazychaser Do you have the latest version of mix? In this commit The md5 chunk hash plugin has been added, so this should no longer be an issue (at least it's not for me).

I wanted files without hash at all, but still with cache busting, like in mix manifest. Dealed with it using following config option: chunkFilename: 'js/[name].js?[chunkhash]'

@lazychaser I had the same issue with newly generated files which just pile up. My solution so far is to delete the folder before compiling again for production, using clean-webpack-plugin.

const CleanWebpackPlugin = require('clean-webpack-plugin')
if (mix.inProduction())
{
    webpackConfig.plugins.push(new CleanWebpackPlugin([
        'public/css',
        'public/js',
    ]));
}

There are a lot more config options for this plugin, so check out https://github.com/johnagan/clean-webpack-plugin.

@dimitri-koenig we actually got the same plugin with a similar config in our project. Would be nice to see it in mix natively (feature request?)

@spaceemotion

Yeah, something like

if (mix.inProduction())
{
    mix.cleanup();
}

would be nice cause you don't need to do this in dev mode, do you? It's the same with mix.version() which you don't need in dev mode, only in production.

Solved the pile-up of files very simple in package.json without any plugins:

  "scripts": {
    "dev": "yarn run development",
    "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "yarn run watch -- --watch-poll",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "prod": "yarn run production",
    "production": "rm -fv public/assets/chunks/* && rm -fv public/assets/js/* && rm -fv public/assets/css/* && cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js && git add public/assets"
  },

Note the git add public/assets in the prod command, I regularly missed chunked files in git (which breaks your app) because I did not pay enough attention while committing :smile:

@neorganic

Is it just me or after implementing this - the npm run watch stopped to work properly(doesn't seem to reflect the code changes)... Now you have to just run npm run dev for full change...

I also have this problem and found out that the problem arises when I implement this:

mix.webpackConfig({
    output: {
        publicPath: '/',
        chunkFilename: 'js/[name].[chunkhash].js',
    },
});

Anyone has an idea how to solve this? My current workaround for that is to delete the generated chunk files in the public folder using this in my webpack.mix.js:

const glob = require('glob')
const del = require('del')

if (process.env.NODE_ENV === 'production') {
    del.sync('public/js/async')
} else {
    glob('public/*([0-9]).js', {}, (err, files) => {
        del.sync(files)
    })
}

and this webpack config:

if (process.env.NODE_ENV === 'production') {
    mix.webpackConfig(webpack => ({
        output: {
            chunkFilename: 'js/async/[name].[chunkhash].js',
        }
    }))
}

so when I use the watcher the chunk files are saved to the public directory and in production when I don't need the watcher the files are saved in my preferred directory.

Just here to say that @stephan-v solution worked for me - I do not know what happened after I upgraded to Mojave - had to re-install xcode, git, node - a real mess. On npm run watch my code compiled fine however, going to my dynamic component page gave me:

[Vue warn]: Failed to resolve async component: function () { return __webpack_require__(".[LOCATION] lazy recursive ^\\.\\/field\\-.*$")("./field-" + _name); } Reason: Error: Loading chunk 0 failed.

And the page did not load. After following the steps by @stephan-v it now compiles and upon browsing to that page it finds 0.js and loads

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hasnatbabur picture hasnatbabur  路  3Comments

amin101 picture amin101  路  3Comments

mstralka picture mstralka  路  3Comments

Micaso picture Micaso  路  3Comments

kpilard picture kpilard  路  3Comments