Slow compilation in a typescript + vue project

The compilation time in my project is around 20s. It could be acceptable if the following compilation times in watch mode were shorter, but they are the same.

It was working fine before I started using typescript + vue (I don’t know exactly which one of those started causing this issue).

It seems that HMR and/or caching are not working as they should.

╭─ sage ./public [00f08bb10023ea657771]
├─ entrypoints
│  ├─ app
│  │  ├─ js/runtime.js                                 48.4 kB
│  │  ├─  371 bytes
│  │  └─ js/app.js                                     1.44 MB
│  ├─ editor
│  │  ├─ js/runtime.js                                 48.4 kB
│  │  ├─  371 bytes
│  │  └─ js/editor.js                                442.36 kB
│  └─ backend
│     ├─ js/runtime.js                                 48.4 kB
│     ├─  371 bytes
│     └─ js/backend.js                                36.07 kB
├─ assets
│  ├─ fonts/subset-AvertaStdCY-ThinItalic.woff        21.95 kB
│  ├─ fonts/subset-AvertaStdCY-LightItalic.woff        21.9 kB
│  ├─ fonts/subset-AvertaStdCY-RegularItalic.woff     21.79 kB
│  ├─ fonts/subset-AvertaStdCY-ExtraboldItalic.woff   21.77 kB
│  └─ fonts/subset-AvertaStdCY-BoldItalic.woff        21.74 kB
│ … 56 additional asset(s) not shown
╰─ compiled 4436 modules in 20s 142ms

╭─ server
├─ proxy:
├─ dev:   http://localhost:3002/
╰─ watching project sources (and 276 other files) 

I’m wondering about the compiled 4436 modules in 20s 142ms part. It seems something is wrong here.

Here’s my setup
bud.config.mjs :

export default async (app) => {
   * Application entrypoints
   * @see {@link}
      app: ['@scripts/app', '@styles/app'],
      editor: ['@scripts/editor', '@styles/editor'],
      backend: ['@styles/backend'],

      URL: 'url-polyfill',
      jquery: ['jQuery', '$'],

      acf: 'window.acf',
      jQuery: 'window.jquery',

    .alias('vue$', app.path('@modules/vue/dist/vue.esm-bundler.js'))

      __VUE_OPTIONS_API__: true, // If you are using the options api.
      __VUE_PROD_DEVTOOLS__: false, // If you don't want people sneaking around your components in production.

     * Directory contents to be included in the compilation
     * @see {@link}

     * URI of the `public` directory
     * @see {@link}

     * Development server settings
     * @see {@link}
     * @see {@link}
     * @see {@link}
    .watch(['resources/views', 'app']);

   * Preserve svg viewBox
   * @see {@link}
  app.imagemin.svgo.set(`encodeOptions`, {
    plugins: [
        name: 'preset-default',
        params: {
          overrides: {
            removeViewBox: false,

   * Generate WordPress `theme.json`
   * @note This overwrites `theme.json` on every build.
   * @see {@link}
   * @see {@link}
    .set('settings.color.custom', false)
    .set('settings.color.customDuotone', false)
    .set('settings.color.customGradient', false)
    .set('settings.color.defaultDuotone', false)
    .set('settings.color.defaultGradients', false)
    .set('settings.color.defaultPalette', false)
    .set('settings.color.duotone', [])
    .set('settings.custom.spacing', {})
    .set('settings.custom.typography.font-size', {})
    .set('settings.custom.typography.line-height', {})
    .set('settings.custom.breakpoints', twConfig.theme?.screens)
    .set('settings.spacing.padding', true)
    .set('settings.spacing.units', ['px', '%', 'em', 'rem', 'vw', 'vh'])
    .set('settings.typography.customFontSize', false)
    .set('', false)


const resolveConfig = require('tailwindcss/resolveConfig.js');
const tailwindConfig = require('./tailwind.config.cjs');

const twConfig = resolveConfig(tailwindConfig);

let plugins = {
  stylelint: {},
  'postcss-bem-linter': {},
  'postcss-at-rules-variables': {atRules: ['for', 'each']},
  'postcss-import-ext-glob': {},
  'postcss-import': {},
  'postcss-for': {},
  'postcss-each': {},
  'postcss-mixins': {},
  'postcss-simple-vars': {},
  'postcss-custom-media': {},
  'tailwindcss/nesting': {},
  tailwindcss: {},
  'postcss-contrast': {
    dark: twConfig.theme?.colors?.dark,
    light: twConfig.theme?.colors?.light,
  'postcss-reporter': {
    clearReportedMessages: true,

if (process.env.NODE_ENV === 'production') {
  plugins = {...plugins, ...{autoprefixer: {}}};

module.exports = {

Is ths situation usual, or I’m I missing something ?

20 seconds doesn’t seem awful based on the little we can assume about your gigantic project :wink:

You will need to do some further troubleshooting on your own and let us know which parts are responsible for the slowness.

I did try to find the culprit, following strarsis answer in this post , but I could find anything that looked unusual.

So I’m wondering if it is normal that all modules get compiled everytime I save a file. I mean, shouldn’t bud recompile only relevant files ? Isn’t it the way HMR is supposed to work ?

Can you provide a minimal reproducible example?

While preparing a minimal repro repo, I found that the count of compiled modules, and thus the build time, dramatically increase when adding some component from the naive-ui framework. I understand that this component requires lots of modules, but why isn’t the cache / HMR process working ? How can I check if they’re working as they should ?

Can you provide an example of how you’re including a component from that framework?

Sure, here are the relevant parts of my code:

import {createApp} from 'vue';
import {pinia} from '@scripts/stores';
import {ref} from 'vue';
import {NIcon, NMenu,NConfigProvider, frFR, dateFrFR} from 'naive-ui';
import type {MenuOption} from 'naive-ui';

export default function () {
  const nav = createApp({
    name: 'GlobalNavigation',
    components: {
      'n-menu': NMenu,
      'n-config-provider': NConfigProvider,
    setup() {
      const mobileNav = ref(null);

      const menuItems = {

      return {

in-DOM templates are read from a blade template.

@LucasDemea You’re really going to need to bring a repo with a minimal reproduction…

Here it is : GitHub - LucasDemea/sage-slow-vue

Of course the build time if much faster in the demo repo as I only left the problematic part (see app.ts) .
But still it is quite slow for such a minimal project, and the build time doesn’t decrease with successive builds in watch mode.

Thanks, we’re taking a look into it

@LucasDemea I submitted a PR against your repo which brings the reload after a save with no changes down to ~50ms.

This was the addition to bud.config.js that improved performance:

    [`naive-ui$`]: app.isDevelopment
      ? app.path(`@modules/naive-ui/dist/index.js`) // commonjs modules are faster to build
      : app.path(`@modules/naive-ui/es/index.js`), // es modules are tree shakeable

Findings from PR as follows:

Tree-shaking is fine. Despite the compiler saying 2330 modules were compiled, most of them are cached. In the next release of bud i’ve added a cached modules count to make it more transparent; running this repo with that build yields this message: compiled 2324 modules (2324 cached) in 1s 872ms.

But, something with naive-ui is weird. You don’t see this issue with buefy or bootstrap-vue (or any of the other vue frontend frameworks I struggled to get working). I think some of this issue might come down to naive-ui’s root module doing namespace re-exports of components:

// ...
export * from './locales';
export * from './components';
export * from './composables';
// ...

and components.js is itself composed entirely of namespace re-exports:

// ...
export * from './breadcrumb';
export * from './button';
export * from './button-group';
export * from './calendar';
// ...

My understanding is that this can be challenging for bundlers and optimizers like webpack and terser when they’re doing static analysis on used exports and side effects. However, I don’t really know 100% what’s going on.

Anyway, my solution is to alias naive-ui imports to naive-ui/dist/index.js in development and naive-ui/es/index.js in production. This prevents the transpiler from having to parse thousands of modules in development while also allowing for tree shaking in production.

    [`naive-ui$`]: app.isDevelopment
      ? app.path(`@modules/naive-ui/dist/index.js`) // commonjs modules are faster to build
      : app.path(`@modules/naive-ui/es/index.js`), // es modules are tree shakeable

I tried a lot of other approaches but this approach was simplest and netted the best results.

I also split up the compilation into discrete bundles so that I could check the final output while working to make sure modules were being tree-shaken. The result seems correct to me:

╭─ sage ./public [4d56282397aca4c37bc5]
├─ entrypoints
│  └─ app
│     ├─ js/runtime.00900e.js                                     1.22 kB
│     ├─ js/bundle/naive-ui-styles/430.688316a1fadc6172b41f.js   10.84 kB
│     ├─ js/bundle/vue/670.a9fb902bd65eeed389c4.js                3.79 kB
│     ├─ js/248.f08eb8.js                                       292.09 kB
│     └─ js/app.c45b99.js                                       399 bytes
╰─ compiled 2323 modules (2323 cached) in 1s 862ms

Running with the --no-minimize flag the header comments all seem to be pointing to relevant modules, and the file sizes seem correct as well.

You can remove the app.bundle calls if you ultimately don’t want this behavior. But, it’s not bad to keep it.


Thank you guys for your time on this issue, I’ll try your solution asap. I’ll also share your findings on the naive-ui GH.

It indeed solved the issue. My average build time went from ~20s to ~8s. Thanks again !