Skip to content

Vue config async support #3328

@nickforddev

Description

@nickforddev

What problem does this feature solve?

Allow for asynchronous actions within vue.config.js. The specific use-case I'm looking at right now is the need to prerender dynamic routes based on data returned by an api. I'm found a few things online but nothing that appears to work with vue cli 3.x

What does the proposed API look like?

Await the result of the configureWebpack or chainWebpack methods:

const path = require('path')
const axios = require('axios')
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  configureWebpack: async () => {
    const { data } = await axios.get('http://some-api.com/companies')
    const { companies } = data
    return {
      plugins: [
        new PrerenderSPAPlugin({
          // Required - The path to the webpack-outputted app to prerender.
          staticDir: path.join(__dirname, 'dist'),
          // Required - Routes to render.
          routes: [ '/' ].concat(companies.map(company => `/companies/${company}`))
        })
      ]
    }
  }
}

Activity

LinusBorg

LinusBorg commented on Jan 19, 2019

@LinusBorg
Member

That could make sense, but would also be a lot of work and possibly a breaking change. We will have to have a thorough look at this.

Meanwhile, you can use a local plugin file to write your own wrapper around build and do the async fetching before actually running build:

package.json:

"vuePlugins": {
  "service": ["build.prerender.js"]
  }

build.prerender.js

module.exports = (api, options) => {
  api.registerCommand('build:prerender', async (args) => {
    const PrerenderSPAPlugin = require('prerender-spa-plugin')
    const { data } = await axios.get('http://some-api.com/companies')
    const { companies } = data
    api.chainWebpack(config => {
      config.plugin('prerender').use(PrerenderSPAPlugin, [{
          // Required - The path to the webpack-outputted app to prerender.
          staticDir: path.join(__dirname, 'dist'),
          // Required - Routes to render.
          routes: [ '/' ].concat(companies.map(company => `/companies/${company}`))
        }])
    })
    
    await api.service.run('build', args)
  })
}

module.exports.defaultModes = {
  'build:prerender': 'production'
}

Usage in package.json:

"scripts": {
  "build": "vue-cli-service build:prerender"
}
nickforddev

nickforddev commented on Jan 21, 2019

@nickforddev
Author

Awesome, I had worked around it with a prebuild npm hook but that required saving the data to the filesystem, your solution is a bit cleaner, thank you.

nickforddev

nickforddev commented on Jan 21, 2019

@nickforddev
Author

@LinusBorg is there any official documentation for this api? I noticed that the build output from your example is significantly different that the output of vue-cli-service build and would like to read more about it.

LinusBorg

LinusBorg commented on Jan 21, 2019

@LinusBorg
Member

The build command itself is completely the same, but of course the pretender plugin changes behaviour.

What differences do you see?

As far as documentation goes, the Plugin dev guide is not as fleshed out yet as we want it to be, but the basics can be found there.

The bit about a local plugin is found in the guide under plugins & presets -> local plugin

nickforddev

nickforddev commented on Jan 21, 2019

@nickforddev
Author

Thank you, I'll take a look. I figured out why the output was different, NODE_ENV was undefined 💯.

What is the best way to deal with that? Seems like vue-cli-service build automatically sets the environment to production, do I have to set the env variable in the npm script manually?

Edit: Asked prematurely, found the answer here https://cli.vuejs.org/dev-guide/plugin-dev.html#service-plugin

LinusBorg

LinusBorg commented on Jan 21, 2019

@LinusBorg
Member

Oh right, forgot to add a default mode for the command. Sorry.

I'll fix my example code.

added 2 commits that reference this issue on Jun 16, 2019
patarapolw

patarapolw commented on Aug 21, 2019

@patarapolw

If need both build:async and serve:async, currently, I have to... (in vue.async.config.js)

function configureWebpack(webpackConfig) {
  ...
}

function defineWebpack(webpackConfig, def) {
  webpackConfig.plugin("define").tap((args) => {
    args[0] = {
      ...args[0],
      ...def
    }
    return args;
  });
}

async function generateDefinitions() {
  await ...
}

module.exports = (api, options) => {
  api.registerCommand('build:async', async (args) => {
    const def = await generateDefinitions();

    api.configureWebpack(configureWebpack);
    api.chainWebpack((webpackConfig) => {
      defineWebpack(webpackConfig, def);
    });
    
    await api.service.run('build', args)
  }),
  api.registerCommand('serve:async', async (args) => {
    const def = await generateDefinitions();

    api.configureWebpack(configureWebpack);
    api.chainWebpack((webpackConfig) => {
      defineWebpack(webpackConfig, def);
    });

    await api.service.run('serve', args)
  })
}

module.exports.defaultModes = {
  'build:async': 'production',
  'serve:async': 'development'
}

There should be a single Vue Config object, therefore, a more elegant way to do this...

githoniel

githoniel commented on Oct 22, 2019

@githoniel
Contributor

I'm write a excel web-addin and it's dev-mode need to load https Cert like below

// vue.config.js
const devCerts = require('office-addin-dev-certs')

module.exports = {
  devServer: {
    port: 3000,
    https:  await devCerts.getHttpsServerOptions()
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
}

I had to split the async code into a new js file and run by node xxx.js to get it work

maybe vue.config.js support exports an async function is a good solution here

WormGirl

WormGirl commented on Aug 5, 2020

@WormGirl

That could make sense, but would also be a lot of work and possibly a breaking change. We will have to have a thorough look at this.

Meanwhile, you can use a local plugin file to write your own wrapper around build and do the async fetching before actually running build:

package.json:

"vuePlugins": {
  "service": ["build.prerender.js"]
  }

build.prerender.js

module.exports = (api, options) => {
  api.registerCommand('build:prerender', async (args) => {
    const PrerenderSPAPlugin = require('prerender-spa-plugin')
    const { data } = await axios.get('http://some-api.com/companies')
    const { companies } = data
    api.chainWebpack(config => {
      config.plugin('prerender').use(PrerenderSPAPlugin, [{
          // Required - The path to the webpack-outputted app to prerender.
          staticDir: path.join(__dirname, 'dist'),
          // Required - Routes to render.
          routes: [ '/' ].concat(companies.map(company => `/companies/${company}`))
        }])
    })
    
    await api.service.run('build', args)
  })
}

module.exports.defaultModes = {
  'build:prerender': 'production'
}

Usage in package.json:

"scripts": {
  "build": "vue-cli-service build:prerender"
}

How to change the devServer option? I want to do something like this:

let devcert = require('devcert')
let ssl = await devcert.certificateFor("localhost", { getCaPath: true });
module.exports = {
  "transpileDependencies": [
    "vuetify"
  ],
  productionSourceMap: false,
  // crossorigin: 'anonymous',
  // publicPath: process.env.VUE_APP_ENV === 'production' ? 'https://w.xx.in/' : '/',
  devServer: {
    https: ssl,
    proxy: {
    }
  }
}

ignore, this is worked.

module.exports = (api, options) => {
  api.registerCommand('serve:prerender', async (args) => {
    let devcert = require('devcert')
    let ssl = await devcert.certificateFor("localhost", { getCaPath: true });
    options.devServer = {
      ...options.devServer,
      https: ssl,
      host: 'localhost'
    }
    await api.service.run('serve', args)
  })
}

module.exports.defaultModes = {
  'serve:prerender': 'development'
}
its-tim-lee

its-tim-lee commented on Dec 26, 2022

@its-tim-lee

That could make sense, but would also be a lot of work and possibly a breaking change. We will have to have a thorough look at this.

Meanwhile, you can use a local plugin file to write your own wrapper around build and do the async fetching before actually running build:

package.json:

"vuePlugins": {
  "service": ["build.prerender.js"]
  }

build.prerender.js

module.exports = (api, options) => {
  api.registerCommand('build:prerender', async (args) => {
    const PrerenderSPAPlugin = require('prerender-spa-plugin')
    const { data } = await axios.get('http://some-api.com/companies')
    const { companies } = data
    api.chainWebpack(config => {
      config.plugin('prerender').use(PrerenderSPAPlugin, [{
          // Required - The path to the webpack-outputted app to prerender.
          staticDir: path.join(__dirname, 'dist'),
          // Required - Routes to render.
          routes: [ '/' ].concat(companies.map(company => `/companies/${company}`))
        }])
    })
    
    await api.service.run('build', args)
  })
}

module.exports.defaultModes = {
  'build:prerender': 'production'
}

Usage in package.json:

"scripts": {
  "build": "vue-cli-service build:prerender"
}

@LinusBorg
What about if I can' use Service Plugin, and I also can't use any webpack compiler hook?
Does Vue Cli provide any way to run once-time async code when I...

  • Start dev (i.e. npm run serve)
  • before compiling

FYI, I knew that can be achieved by using Vite's configResolved.
But does that mean Vue Cli can't do that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @nickforddev@LinusBorg@WormGirl@githoniel@patarapolw

        Issue actions

          Vue config async support · Issue #3328 · vuejs/vue-cli