Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Hello and thanks for this gem,

our app consists of multiple Vue single-page apps, some of which we are currently finally migrating from Vue 2 to Vue 3.

As a first step, we are migrating the bundling from Webpack to Vite for the Vue 2 apps. (Another Vue 3 app is already on Vite.) Because we need different Vite plugins (e.g. the Vue 2 one) for each app, I think I need a different Vite config file for each, essentially two Vite build processes. Native Vite supports this with e.g. vite build --config other.file.config.js.

Can this somehow be achieved with vite_ruby? Or is there another attempt I can try out?

You must be logged in to vote

Replies: 1 comment · 7 replies

Comment options

Hi Franz!

It's possible, but it's not supported out of the box, it requires a bespoke setup.

I'd recommend that you try restricting the vue plugins via include and exclude, so that the Vue 2 one only processes the old code, while the Vue 3 only processes the new code (ideally place the code in different dirs, and use regexes based on those dirs).

If you need a separate bundle for each app...

An approach I'm using in a Rails app that requires 6 different builds involves manually specifying the entrypoints per "bundle":

const bundles = {
  admin: {
    entrypoints: [
      'entrypoints/admin.ts',
    ],
  },
  main: {
    entrypoints: [
      'entrypoints/main.ts',
      'entrypoints/main_styles.scss',
    ],
  },
  // 4 additional apps.
}

const separateBundlesPlugin: Plugin = {
  name: 'build:separate-bundles',
  apply: 'build',
  enforce: 'post',
  configResolved ({ build: { rollupOptions } }) {
    const bundleName = process.env.VITE_RUBY_BUNDLE_NAME
    const bundleConfig = bundles[bundleName]

    if (!bundleConfig)
      throw new Error(`Unknown frontend bundle ${bundleName}.`)

    console.info('Building', { bundleName, ...bundleConfig })

    rollupOptions.input = Object.fromEntries(Object.entries(rollupOptions.input)
      .filter(([name, value]) => bundleConfig.entrypoints.includes(name)))

    console.info(rollupOptions.input)
  },
}

That way I can start a single dev server and use all "apps" in development, while at build time each one gets its own manifest:

# app/helpers/vite_build_helper.rb

# Override: We want to use separate builds for each frontend app, since the
# users of these apps don't have a lot of overlap, and we want to optimize each
# bundle separately.
module ViteBuildHelper
  # Override: Returns a different ViteRuby instance for each app.
  #
  # In development, we want the convenience of a single dev server.
  # In production, use a separate build output dir and manifest for each app.
  def vite_ruby_instance
    @vite_ruby_instance ||= if Rails.env.local? && ViteRuby.instance.dev_server_running?
      ViteRuby.instance
    else
      ViteBuildHelper.instances[vite_bundle_name]
    end
  end

  # Internal: Infer the corresponding Vite bundle based on the controller's config, shared through inheritance.
  def vite_bundle_name
    controller.vite_bundle_name
  end

  class << self
    def instances
      @instances ||= Hash.new { |instances, bundle_name|
        instances[bundle_name] = new_instance(name)
      }
    end

    def new_instance(name)
      config = ViteRuby::Config.resolve_config

      ViteRuby.new(
        auto_build: Rails.env.development? || (Rails.env.test? && !ENV["CI"]),
        public_output_dir: "#{config.public_output_dir}/#{name}",
        build_cache_dir: "tmp/cache/vite/#{name}",
      ).tap do |instance|
        instance.define_singleton_method(:bundle_name) { name }
        instance.env["VITE_RUBY_BUNDLE_NAME"] = name.to_s
        instance.logger = ViteRuby.instance.logger
      end
    end
  end
end

In this case, I also take advantage of the fact that I can configure certain plugins to only process a portion of the app (via include, exclude), so for example, I have six instances of unplugin-vue-components configured differently.

If possible, I'd suggest trying to find a different approach, as this gets quite complex.

You must be logged in to vote
7 replies
@franzliedke
Comment options

Looks like include/exclude won't be usable for the vue2 plugin, this person has the exact same problem: vitejs/vite-plugin-vue2#102

@ElMassimo
Comment options

In that case, using two separate builds:

bin/vite build -- --config vite-vue2.config.ts

You will probably need two Vite dev servers, along with a way to manage two ViteRuby.instance that render script tags targeting one or the other depending on which "app" should be rendered.

Simplifying how that might look at build time:

task build_vue_apps: :'vite:verify_install' do
  ViteRuby.vue3_instance.commands.build_from_task # ("--config", "vite.config.ts") 
  ViteRuby.vue2_instance.commands.build_from_task("--config", "vite-vue2.config.ts") 
end

Supporting this in Vite Ruby out of the box is definitely out of scope, but as I mentioned before, it's already possible to get something like this working given you can have multiple instances of ViteRuby and configure them differently.

Since you will need two different dev servers, you might as well simplify the ViteBuildHelper example above and customize entrypointsDir differently for each app so that the Vue2 and Vue3 entrypoints are separate, by passing entrypoints_dir to ViteRuby.new, allowing you to skip a manual list of entrypoints as in the example above.

@zealot128
Comment options

Great starters @ElMassimo !

We want to reintegrate several different Rails Apps (That already are built by Vite-Rails) back into one Monolith using "packs" (Similar to engines, but co-located in packs/NAME/app/javascript) to simplify our inter-apps dependencies, synchronization and deployments.

We already had great success with configuring Vite to also look there via aliases (e.g. aliases: { "app_name": "packs/app_name/app/javascript/*" } }) and TSConfig-paths, but for some bigger reintegrated apps, that have a different frontend, design, we'd like to supply a completely different package.json + vite.config.ts in the pack root file. (like packs/NAME/package.json + packs/NAME/vite.config.ts + packs/NAME/.postcss.yml)

Why different package.json: Because there might be older Bootstrap4 oder Vue 2.7, vs. Vue 3 etc. in the app together that would conflict in a singular package.json

If you ever have built something like that, I would be grateful if you could share your experiences. Especially regarding separating the package.json or bin/vite cli tool, like bin/vite-SUBAPP etc.
Otherwise, I will report back my findings in January when we try to accomplish this :)

@ElMassimo
Comment options

Haven't had the need for separate config files, but should be easy to achieve if you use a custom binstub that can set the --config flag for Vite accordingly.

Have in mind that you can use imports in the Vite configs to share plugins and configuration, both for consistency and to avoid duplication.

@zealot128
Comment options

Using the hints in the discussion here and over in #95 I made big leap of progress after fiddling around with different approaches, to make sure all the paths are right and do not clash with main-app's Vite.

Our constraints/prereqs:

  • using packs (integrated Rails app) + packs-rails
  • some pack have a completely different frontend, so it is nice to be able to change libraries here
  • using AutoBuild instead of extra dev server - To much hassle with websocket port forwardings in a https reverse proxy that we use. That is mostly for a finished/static project only, so that's fine.
    • source: ./packs/PACK_NAME/app/javascript
    • building assets into MAIN-APP/public/vite-packs/PACK_NAME/vite
  • completely different vite.config.ts + yarn.lock + package.json etc. over in the pack, so no issue with different Vue, Bootstrap whatever version. That pack's js can now move more slowly than the main app, and does not block upgrades on the main app either.
  • same vite_ruby vite_rails version still required, as there can only be one Ruby Gem loaded - Was a small problem, with the change to Vite 5 - Cjs stuff with some ancient packages that we had to come around.

Separate Vite instance builder

somewhere make the ViteRuby builder, similar like ViteRuby's own instance.

module MyPack
   def self.vite_ruby
    @vite_ruby ||=
      ViteRuby.new(
        root: Rails.root.join('packs/MYPACK'),
        public_dir: Rails.root.join('./public/'),
        auto_build: !Rails.env.production?,
        # different port here for us, because we also use global VITE_* env vars to configure proxy ports, 
        # otherwise picks up pl server stuff, and vite thinks dev-server is running.
        port: 55555,
        public_output_dir: 'vite-packs/MYPACK/vite',
        build_cache_dir: '../../tmp/cache/vite/packs/MYPACK',
        logger: Rails.logger
      )
  end
  • You can still set some other options, like entrypoints or sourceCodeDir in ./packs/MYPACK/config/vite.json
  • copy over vite.config.ts, tsconfig, package.json but make sure Vite+deps have some dependency like main-app, as ViteRuby expects the versions
  • I needed to migrate from .postcssrc.yaml to postcss.config.js, otherwise there were some strange YAML build errors.

Check if it builds:

MyPack.vite_ruby.commands.build

Reader's task: create a rake task or so to run that.

Override vite_manifest for views:

In the base-controller of our pack:

class MyPack::BaseController < ApplicationController
  ... stuff like layout
  private 

  helper_method def vite_manifest 
     MyPack.vite_ruby.manifest
  end
end

... and it just works! We can now use vite_typescript_tag etc. in our views that got rendered from that controllers children's views.

Missing step: Fiddle it into assets:precompile task, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.