├── .github ├── FUNDING.yml └── workflows │ ├── gempush.yml │ ├── cd.yml │ ├── docker_github.yml │ └── test.yml ├── package ├── environments │ ├── test.js │ ├── development.js │ ├── production.js │ └── base.js ├── configPath.js ├── utils.js ├── index.js ├── env.js └── config.js ├── .gitmodules ├── Gemfile ├── bin └── test ├── .gitignore ├── config ├── routes.rb ├── vite_default.yml └── vite_template.yml ├── test ├── viter_test.rb └── test_helper.rb ├── Rakefile ├── lib ├── tasks │ └── viter_tasks.rake ├── rails_vite │ ├── engine.rb │ ├── template_renderer.rb │ ├── runner.rb │ ├── instance.rb │ ├── dev_server.rb │ ├── env.rb │ ├── dev_server_runner.rb │ ├── exporter.rb │ ├── commands.rb │ ├── configuration.rb │ ├── yaml.rb │ ├── compiler.rb │ └── manifest.rb └── rails_vite.rb ├── package.gemspec ├── app ├── controllers │ └── vite │ │ └── common_controller.rb └── helpers │ └── vite │ ├── assets_helper.rb │ └── application_helper.rb ├── package.json ├── README.md ├── LICENSE ├── Gemfile.lock └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ['qinmingyuan'] 2 | -------------------------------------------------------------------------------- /package/environments/test.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./base') 2 | 3 | module.exports = baseConfig 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/dummy"] 2 | path = test/dummy 3 | url = https://github.com/work-design/dummy.git 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | gemspec 4 | 5 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.expand_path('../test', __dir__) 3 | 4 | require 'bundler/setup' 5 | require 'rails/plugin/test' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | docs/ 3 | db/ 4 | node_modules/ 5 | log/*.log 6 | tmp/**/*.* 7 | !tmp/.keep 8 | !tmp/sockets/.keep 9 | 10 | *.gem 11 | -------------------------------------------------------------------------------- /package/configPath.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = process.env.RAILS_VITE_CONFIG || resolve('config', 'vite.yml') 4 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get '@fs/*path' => 'vite/common#index' 3 | get 'images/*path' => 'vite/common#image' 4 | end 5 | -------------------------------------------------------------------------------- /test/viter_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ViterTest < ActiveSupport::TestCase 4 | test "it has a version number" do 5 | assert RailsVite::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/vite_default.yml: -------------------------------------------------------------------------------- 1 | # Vite 默认配置 2 | default: &default 3 | public_root_path: public 4 | cache_path: tmp/cache/rails_vite 5 | development: 6 | <<: *default 7 | test: 8 | <<: *default 9 | production: 10 | <<: *default 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | require "bundler/gem_tasks" 4 | 5 | require "rake/testtask" 6 | 7 | Rake::TestTask.new(:test) do |t| 8 | t.libs << 'test' 9 | t.pattern = 'test/**/*_test.rb' 10 | t.verbose = false 11 | end 12 | 13 | task default: :test 14 | -------------------------------------------------------------------------------- /lib/tasks/viter_tasks.rake: -------------------------------------------------------------------------------- 1 | namespace :rails_vite do 2 | 3 | desc 'RailsVite Build' 4 | task build: [:environment] do 5 | RailsVite.compile 6 | end 7 | 8 | desc 'rails_vite clobber' 9 | task clobber: [:environment] do 10 | RailsVite.clobber 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /package/environments/development.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./base') 2 | const config = require('../config') 3 | const devServer = config.server 4 | 5 | const devConfig = { 6 | mode: 'development', 7 | server: { 8 | ...devServer, 9 | strictPort: true // 禁止自动切换 port 的功能, rails 需要加载需要 10 | } 11 | } 12 | 13 | module.exports = { 14 | ...baseConfig, 15 | ...devConfig 16 | } 17 | -------------------------------------------------------------------------------- /lib/rails_vite/engine.rb: -------------------------------------------------------------------------------- 1 | require 'rails_com' 2 | 3 | module RailsVite 4 | class Engine < ::Rails::Engine 5 | config.vite = ActiveSupport::OrderedOptions.new 6 | 7 | initializer 'rails_vite.bootstrap' do 8 | if defined?(Rails::Server) || defined?(Rails::Console) 9 | #RailsVite::Exporter.export 10 | #RailsVite.bootstrap 11 | end 12 | end 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rails_vite/template_renderer.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | module TemplateRenderer 3 | 4 | def render_template(view, template, layout_name, locals) 5 | view.instance_variable_set(:@_rendered_template_path, template.identifier) 6 | super 7 | end 8 | 9 | end 10 | end 11 | 12 | ActiveSupport.on_load :action_view do 13 | ActionView::TemplateRenderer.prepend RailsVite::TemplateRenderer 14 | end 15 | -------------------------------------------------------------------------------- /config/vite_template.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | root_path: app/entrypoints 3 | public_root_path: public 4 | cache_path: tmp/cache/vite 5 | entry_paths: [] 6 | alias: 7 | abcs: ../public 8 | build: 9 | assetsInlineLimit: 0 10 | sourcemap: false 11 | assetsDir: assets 12 | brotliSize: true 13 | development: 14 | <<: *default 15 | server: 16 | host: localhost 17 | port: 3036 18 | fs: 19 | strict: false 20 | test: 21 | <<: *default 22 | production: 23 | <<: *default 24 | -------------------------------------------------------------------------------- /package.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'rails_vite' 3 | s.version = '0.0.1' 4 | s.authors = ['qinmingyuan'] 5 | s.email = ['mingyuan0715@foxmail.com'] 6 | s.homepage = 'https://github.com/work-design/rails_vite' 7 | s.summary = 'Vite for Rails' 8 | s.description = 'Description of RailsVite.' 9 | s.license = 'MIT' 10 | 11 | s.files = Dir[ 12 | '{app,config,db,lib}/**/*', 13 | 'LICENSE', 14 | 'Rakefile', 15 | 'README.md' 16 | ] 17 | 18 | s.add_dependency 'rails' 19 | end 20 | -------------------------------------------------------------------------------- /package/utils.js: -------------------------------------------------------------------------------- 1 | const { basename, dirname, join, relative, resolve } = require('path') 2 | const { sync: globSync } = require('glob') 3 | const extname = require('path-complete-extname') 4 | 5 | const getEntryObject = (rootPath) => { 6 | const entries = {} 7 | 8 | globSync(`${rootPath}/**/*.+(js|ts)`).forEach((path) => { 9 | const namespace = relative(join(rootPath), dirname(path)) 10 | const name = join(namespace, basename(path, extname(path))) 11 | 12 | entries[name] = resolve(path) 13 | }) 14 | 15 | return entries 16 | } 17 | 18 | module.exports = getEntryObject 19 | -------------------------------------------------------------------------------- /package/index.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const { resolve } = require('path') 5 | const { existsSync } = require('fs') 6 | const baseConfig = require('./environments/base') 7 | const config = require('./config') 8 | const { nodeEnv } = require('./env') 9 | 10 | const viteConfig = () => { 11 | const path = resolve(__dirname, 'environments', `${nodeEnv}.js`) 12 | const environmentConfig = existsSync(path) ? require(path) : baseConfig 13 | return environmentConfig 14 | } 15 | 16 | module.exports = { 17 | config, 18 | viteConfig: viteConfig(), 19 | baseConfig 20 | } 21 | -------------------------------------------------------------------------------- /app/controllers/vite/common_controller.rb: -------------------------------------------------------------------------------- 1 | module Vite 2 | class CommonController < ActionController::Base 3 | 4 | def index 5 | path = Pathname.new "/#{params[:path]}.#{params[:format]}" 6 | 7 | if path.exist? && Rails.env.development? 8 | send_file path 9 | else 10 | head :ok 11 | end 12 | end 13 | 14 | def image 15 | file = "#{params[:path]}.#{params[:format]}" 16 | path = Rails.root.join('app/assets', 'images', file) 17 | 18 | if path.exist? && Rails.env.development? 19 | send_file path 20 | else 21 | head :ok 22 | end 23 | end 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /.github/workflows/gempush.yml: -------------------------------------------------------------------------------- 1 | name: Gem 2 | on: 3 | create: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | build: 8 | name: Build + Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Fetch Project 12 | uses: actions/checkout@v2 13 | with: 14 | submodules: true 15 | - name: Set up Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 3.0 19 | - name: Publish to RubyGems 20 | run: | 21 | mkdir -p $HOME/.gem 22 | touch $HOME/.gem/credentials 23 | chmod 0600 $HOME/.gem/credentials 24 | printf -- "---\n:rubygems_api_key: ${{ secrets.GEM_KEY }}\n" > $HOME/.gem/credentials 25 | gem build *.gemspec 26 | gem push *.gem 27 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require_relative "../test/dummy/config/environment" 5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] 6 | require "rails/test_help" 7 | 8 | require "rails/test_unit/reporter" 9 | Rails::TestUnitReporter.executable = 'bin/test' 10 | 11 | # Load fixtures from the engine 12 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 13 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) 14 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 15 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" 16 | ActiveSupport::TestCase.fixtures :all 17 | end 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rails_vite", 3 | "version": "0.0.6", 4 | "description": "Vite for Rails", 5 | "main": "package/index.js", 6 | "files": [ 7 | "package", 8 | "config" 9 | ], 10 | "dependencies": { 11 | "glob": "^7.1.7", 12 | "js-yaml": "^4.1.0", 13 | "path-complete-extname": "^1.0.0", 14 | "vite": "^2.9.1" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "jest", 19 | "lint": "eslint package/" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/work-design/rails_vite.git" 24 | }, 25 | "author": "mingyuanqin", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/work-design/rails_vite/issues" 29 | }, 30 | "homepage": "https://github.com/work-design/rails_vite" 31 | } 32 | -------------------------------------------------------------------------------- /lib/rails_vite/runner.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class Runner 3 | 4 | def self.run(argv) 5 | $stdout.sync = true 6 | 7 | new(argv).run 8 | end 9 | 10 | def initialize(argv) 11 | @argv = argv 12 | 13 | @app_path = File.expand_path('.', Dir.pwd) 14 | @node_modules_bin_path = ENV['VITER_NODE_MODULES_BIN_PATH'] || `yarn bin`.chomp 15 | @vite_config = File.join(@app_path, "config/vite/#{ENV['NODE_ENV']}.js") 16 | @viter_config = File.join(@app_path, 'config/vite.yml') 17 | 18 | unless File.exist?(@vite_config) 19 | $stderr.puts "vite config #{@vite_config} not found, please run 'bundle exec rails vite:install' to install RailsVite with default configs or add the missing config file for your custom environment." 20 | exit! 21 | end 22 | end 23 | 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RailsVite 2 | 3 | Rails Frontend Develop suit Make you so happy 4 | Use [Vite](https://github.com/vitejs/vite), 5 | Inspired by [webpacker](https://github.com/rails/webpacker), But more powerful: 6 | 7 | * for Engine develop: you can import assets files under `app/assets` 8 | * You can put js or css files under `app/views`, same name as action, which will load automatically. 9 | 10 | 11 | ## How to Use 12 | 13 | * Install 14 | * Add `gem 'rails_vite'` to you gemfile, then bundle it; 15 | * Run `yarn add https://github.com/qinmingyuan/rails_vite` add `rails_vite` to package.json 16 | * In Production 17 | * 编译 Asset:`env RAILS_ENV=production rake rails_vite:compile` 18 | 19 | 3. Enjoy it; 20 | 21 | 22 | 23 | ## License 24 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 25 | -------------------------------------------------------------------------------- /package/environments/production.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const baseConfig = require('./base') 5 | const config = require('../config') 6 | const getEntryObject = require('../utils') 7 | const { join } = require('path') 8 | 9 | const paths = () => { 10 | const result = {} 11 | 12 | baseConfig.entry_paths.forEach((entry_path) => { 13 | Object.assign(result, getEntryObject(entry_path)) 14 | }) 15 | 16 | return result 17 | } 18 | 19 | const productionConfig = { 20 | build: { 21 | ...config.build, 22 | manifest: true, 23 | emptyOutDir: false, 24 | outDir: join(process.cwd(), config.public_root_path), 25 | rollupOptions: { 26 | input: { 27 | ...paths() 28 | } 29 | } 30 | } 31 | } 32 | 33 | module.exports = Object.assign(baseConfig, productionConfig) 34 | -------------------------------------------------------------------------------- /package/env.js: -------------------------------------------------------------------------------- 1 | const yaml = require('js-yaml') 2 | const { readFileSync } = require('fs') 3 | 4 | const NODE_ENVIRONMENTS = ['development', 'production', 'test'] 5 | const DEFAULT = 'development' 6 | const configPath = require('./configPath') 7 | 8 | const railsEnv = process.env.RAILS_ENV 9 | const rawNodeEnv = process.env.NODE_ENV 10 | const nodeEnv = rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv) ? rawNodeEnv : DEFAULT 11 | const isProduction = nodeEnv === 'production' 12 | const isDevelopment = nodeEnv === 'development' 13 | 14 | const config = yaml.load(readFileSync(configPath), 'utf8') 15 | const availableEnvironments = Object.keys(config).join('|') 16 | const regex = new RegExp(`^(${availableEnvironments})$`, 'g') 17 | 18 | module.exports = { 19 | railsEnv: railsEnv && railsEnv.match(regex) ? railsEnv : DEFAULT, 20 | nodeEnv, 21 | isProduction, 22 | isDevelopment 23 | } 24 | -------------------------------------------------------------------------------- /lib/rails_vite/instance.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class Instance 3 | cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) } 4 | 5 | attr_reader :root_path, :config_path 6 | 7 | def initialize(root_path: Rails.root, config_path: Rails.root.join('config/vite.yml')) 8 | @root_path = root_path 9 | @config_path = config_path 10 | end 11 | 12 | def env 13 | @env ||= Env.inquire self 14 | end 15 | 16 | def config 17 | @config ||= Configuration.new( 18 | root_path: root_path, 19 | config_path: config_path, 20 | env: env 21 | ) 22 | end 23 | 24 | def compiler 25 | @compiler ||= Compiler.new self 26 | end 27 | 28 | def dev_server 29 | @dev_server ||= DevServer.new config 30 | end 31 | 32 | def manifest 33 | @manifest ||= Manifest.new self 34 | end 35 | 36 | def commands 37 | @commands ||= Commands.new self 38 | end 39 | 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /package/config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const yaml = require('js-yaml') 3 | const { readFileSync } = require('fs') 4 | const { railsEnv } = require('./env') 5 | const configPath = require('./configPath') 6 | 7 | const defaultConfigPath = require.resolve('../config/vite_default.yml') 8 | 9 | const getDefaultConfig = () => { 10 | const defaultConfig = yaml.load(readFileSync(defaultConfigPath), 'utf8') 11 | return defaultConfig[railsEnv] || defaultConfig.production 12 | } 13 | 14 | const defaults = getDefaultConfig() 15 | const app = yaml.load(readFileSync(configPath), 'utf8')[railsEnv] 16 | 17 | const config = Object.assign(defaults, app) 18 | 19 | // Ensure that the publicPath includes our asset host so dynamic imports 20 | // (code-splitting chunks and static assets) load from the CDN instead of a relative path. 21 | const getPublicPath = () => { 22 | const rootUrl = process.env.VITER_ASSET_HOST || '/' 23 | return `${rootUrl}${config.public_output_path}/` 24 | } 25 | 26 | config.publicPath = getPublicPath() 27 | config.publicPathWithoutCDN = `/${config.public_output_path}/` 28 | 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-Present Mingyuan Qin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package/environments/base.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const { join } = require('path') 5 | const config = require('../config') 6 | 7 | const resolvedPath = (packageName) => { 8 | try { 9 | return require.resolve(packageName) 10 | } catch (e) { 11 | if (e.code !== 'MODULE_NOT_FOUND') { 12 | throw e 13 | } 14 | return null 15 | } 16 | } 17 | 18 | const getPlugins = () => { 19 | const plugins = [] 20 | 21 | if (resolvedPath('vite-plugin-compress')) { 22 | const viteCompression = require('vite-plugin-compress').default 23 | plugins.push( 24 | viteCompression() 25 | ) 26 | } 27 | 28 | if (resolvedPath('@vitejs/plugin-vue')) { 29 | const vue = require('@vitejs/plugin-vue').default 30 | plugins.push( 31 | vue() 32 | ) 33 | } 34 | 35 | return plugins 36 | } 37 | 38 | module.exports = { 39 | root: join(process.cwd(), config.root_path), 40 | base: config.base || '/', 41 | entry_paths: config.entry_paths, 42 | plugins: getPlugins(), 43 | resolve: { 44 | alias: config.alias 45 | }, 46 | css: { 47 | postcss: join(process.cwd(), 'postcss.config.js') 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/rails_vite/dev_server.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class DevServer 3 | 4 | # Configure dev server connection timeout (in seconds), default: 0.01 5 | # Webpacker.server.connect_timeout = 1 6 | # cattr_accessor(:connect_timeout) { 0.01 } 7 | attr_reader :config 8 | 9 | def initialize(config) 10 | @config = config 11 | end 12 | 13 | def running? 14 | if config.server.present? 15 | Socket.tcp(host, port, connect_timeout: connect_timeout).close 16 | true 17 | else 18 | false 19 | end 20 | rescue 21 | false 22 | end 23 | 24 | def host 25 | fetch(:host) 26 | end 27 | 28 | def port 29 | fetch(:port) 30 | end 31 | 32 | def https? 33 | case fetch(:https) 34 | when true, 'true', Hash 35 | true 36 | else 37 | false 38 | end 39 | end 40 | 41 | def protocol 42 | https? ? 'https' : 'http' 43 | end 44 | 45 | def host_with_port 46 | "#{host}:#{port}" 47 | end 48 | 49 | def pretty? 50 | fetch(:pretty) 51 | end 52 | 53 | private 54 | def fetch(key) 55 | config.fetch(key) 56 | end 57 | 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - deploy 7 | jobs: 8 | build: 9 | name: Build image 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Docker login 13 | uses: docker/login-action@v1 14 | with: 15 | registry: ${{ secrets.REGISTRY }} 16 | username: ${{ secrets.USER }} 17 | password: ${{ secrets.TOKEN }} 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | with: 21 | submodules: true 22 | - name: Docker build 23 | run: | 24 | docker build -t ${{ secrets.DOCKER_URL }}/$GITHUB_REPOSITORY:latest -f docker/engine/Dockerfile . 25 | - name: Docker push latest 26 | if: ${{ github.ref == 'refs/heads/main' }} 27 | run: | 28 | docker push ${{ secrets.DOCKER_URL }}/$GITHUB_REPOSITORY:latest 29 | - name: Docker push production 30 | if: ${{ github.ref == 'refs/heads/deploy' }} 31 | run: | 32 | docker tag ${{ secrets.DOCKER_URL }}/$GITHUB_REPOSITORY:latest ${{ secrets.DOCKER_URL }}/$GITHUB_REPOSITORY:production 33 | docker push ${{ secrets.DOCKER_URL }}/$GITHUB_REPOSITORY:production 34 | -------------------------------------------------------------------------------- /lib/rails_vite/env.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class Env 3 | DEFAULT = 'production'.freeze 4 | 5 | delegate :config_path, :logger, to: :@rails_vite 6 | 7 | def self.inquire(rails_vite) 8 | new(rails_vite).inquire 9 | end 10 | 11 | def initialize(rails_vite) 12 | @rails_vite = rails_vite 13 | end 14 | 15 | def inquire 16 | fallback_env_warning if config_path.exist? && !current 17 | current || DEFAULT.inquiry 18 | end 19 | 20 | private 21 | def current 22 | Rails.env.presence_in(available_environments) 23 | end 24 | 25 | def fallback_env_warning 26 | logger.info "RAILS_ENV=#{Rails.env} environment is not defined in config/rails_vite.yml, falling back to #{DEFAULT} environment" 27 | end 28 | 29 | def available_environments 30 | if config_path.exist? 31 | begin 32 | YAML.load_file(config_path.to_s, aliases: true) 33 | rescue ArgumentError 34 | YAML.load_file(config_path.to_s) 35 | end 36 | else 37 | [].freeze 38 | end 39 | rescue Psych::SyntaxError => e 40 | raise "YAML syntax error occurred while parsing #{config_path}. " \ 41 | "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ 42 | "Error: #{e.message}" 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /.github/workflows/docker_github.yml: -------------------------------------------------------------------------------- 1 | name: Docker Github 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | name: Build image 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Docker login 12 | uses: docker/login-action@v1 13 | with: 14 | registry: docker.pkg.github.com 15 | username: ${{ github.actor }} 16 | password: ${{ secrets.AK }} 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Docker build 22 | run: | 23 | docker build -t docker.pkg.github.com/$GITHUB_REPOSITORY/${{ github.event.repository.name }}:latest -f docker/engine/Dockerfile . 24 | - name: Docker push latest 25 | if: ${{ github.ref == 'refs/heads/main' }} 26 | run: | 27 | docker push docker.pkg.github.com/$GITHUB_REPOSITORY/${{ github.event.repository.name }}:latest 28 | - name: Docker push production 29 | if: ${{ github.ref == 'refs/heads/deploy' }} 30 | run: | 31 | docker tag docker.pkg.github.com/$GITHUB_REPOSITORY/${{ github.event.repository.name }}:latest docker.pkg.github.com/$GITHUB_REPOSITORY/${{ github.event.repository.name }}:production 32 | docker push docker.pkg.github.com/$GITHUB_REPOSITORY/${{ github.event.repository.name }}:production 33 | -------------------------------------------------------------------------------- /lib/rails_vite.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/module/attribute_accessors' 2 | require 'active_support/core_ext/string/inquiry' 3 | require 'active_support/logger' 4 | require 'active_support/tagged_logging' 5 | 6 | module RailsVite 7 | extend self 8 | 9 | def instance=(instance) 10 | @instance = instance 11 | end 12 | 13 | def instance 14 | @instance ||= RailsVite::Instance.new 15 | end 16 | 17 | def with_node_env(env) 18 | original = ENV['NODE_ENV'] 19 | ENV['NODE_ENV'] = env 20 | yield 21 | ensure 22 | ENV['NODE_ENV'] = original 23 | end 24 | 25 | def ensure_log_goes_to_stdout 26 | old_logger = RailsVite.logger 27 | RailsVite.logger = ActiveSupport::Logger.new(STDOUT) 28 | yield 29 | ensure 30 | RailsVite.logger = old_logger 31 | end 32 | 33 | delegate :logger, :logger=, :env, to: :instance 34 | delegate :config, :compiler, :manifest, :commands, :dev_server, to: :instance 35 | delegate :bootstrap, :clean, :clobber, :compile, to: :commands 36 | end 37 | 38 | require 'rails_vite/instance' 39 | require 'rails_vite/env' 40 | require 'rails_vite/configuration' 41 | require 'rails_vite/manifest' 42 | require 'rails_vite/compiler' 43 | require 'rails_vite/commands' 44 | require 'rails_vite/dev_server' 45 | require 'rails_vite/exporter' 46 | require 'rails_vite/engine' 47 | require 'rails_vite/template_renderer' 48 | require 'rails_vite/dev_server_runner' 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | services: 7 | postgres: 8 | image: postgres:13 9 | env: 10 | POSTGRES_USER: postgres 11 | POSTGRES_PASSWORD: postgres 12 | options: >- 13 | --health-cmd pg_isready 14 | --health-interval 10s 15 | --health-timeout 5s 16 | --health-retries 5 17 | ports: 18 | - 5432:5432 19 | steps: 20 | - name: Fetch Project 21 | uses: actions/checkout@v2 22 | with: 23 | submodules: true 24 | - name: Set up Ruby and Bundle Install 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: 3.0 28 | bundler-cache: true 29 | - name: Set up Nodejs 30 | uses: actions/setup-node@v2 31 | with: 32 | node-version: 16.3 33 | - name: Cache Node Modules 34 | uses: actions/cache@v2 35 | with: 36 | path: test/dummy/node_modules 37 | key: ${{ runner.OS }}-yarn-${{ hashFiles('test/dummy/yarn.lock') }} 38 | - name: Install Javascript(Node.js) Dependencies 39 | run: | 40 | test/dummy/bin/yarn install --check-files 41 | - name: Prepare DB 42 | run: | 43 | RAILS_ENV=test bin/rails app:db:prepare 44 | RAILS_ENV=test bin/rails g rails_extend:migrations -f 45 | RAILS_ENV=test bin/rails db:migrate 46 | - name: Run Test 47 | run: | 48 | bin/rails test -v -b 49 | -------------------------------------------------------------------------------- /lib/rails_vite/dev_server_runner.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | require 'socket' 3 | require 'rails_vite/configuration' 4 | require 'rails_vite/dev_server' 5 | require 'rails_vite/runner' 6 | 7 | module RailsVite 8 | class DevServerRunner < Runner 9 | 10 | def run 11 | load_config 12 | execute_cmd 13 | end 14 | 15 | private 16 | 17 | def load_config 18 | app_root = Pathname.new(@app_path) 19 | 20 | @config = Configuration.new( 21 | root_path: app_root, 22 | config_path: app_root.join('config/vite.yml'), 23 | env: ENV['RAILS_ENV'] 24 | ) 25 | 26 | server = DevServer.new(@config) 27 | @hostname = server.host 28 | @port = server.port 29 | @pretty = server.pretty? 30 | @https = server.https? 31 | 32 | rescue Errno::ENOENT, NoMethodError 33 | $stdout.puts "vite configuration not found in #{@config.config_path}[#{ENV["RAILS_ENV"]}]." 34 | $stdout.puts "Please run bundle exec rails rails_vite:install to install rails_vite" 35 | exit! 36 | end 37 | 38 | def execute_cmd 39 | cmd = ['yarn', 'vite'] 40 | 41 | if @argv.include?('--debug-rails_vite') 42 | cmd = ['node', '--inspect-brk'] + cmd 43 | @argv.delete '--debug-rails_vite' 44 | end 45 | 46 | cmd += @argv 47 | cmd += ['--config', @vite_config] 48 | cmd += ['--progress', '--color'] if @pretty 49 | 50 | Dir.chdir(@app_path) do 51 | Kernel.exec *cmd 52 | end 53 | end 54 | 55 | def node_modules_bin_exist? 56 | File.exist?("#{@node_modules_bin_path}/vite") 57 | end 58 | 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /app/helpers/vite/assets_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Vite 3 | module AssetsHelper 4 | 5 | # Assets path: app/assets/javascripts/controllers 6 | def pack_path(ext:, **options) 7 | path, ext = assets_load_path(ext: ext, suffix: options.delete(:suffix)) 8 | 9 | asset_vite_path(path + ext) 10 | end 11 | 12 | def js_load(ext: '.js', **options) 13 | path, _ = assets_load_path(ext: ext, suffix: options.delete(:suffix)) 14 | options[:host] = RailsVite.instance.config.host if dev_host? 15 | 16 | if path 17 | javascript_include_tag("/#{path}", type: 'module', **options) 18 | end 19 | end 20 | 21 | # Assets path: app/assets/stylesheets/controllers 22 | def css_load(ext: '.scss', **options) 23 | path, _ = assets_load_path(ext: ext, suffix: options.delete(:suffix)) 24 | options[:host] = RailsVite.instance.config.host if dev_host? 25 | 26 | if path 27 | stylesheet_link_tag("/#{path}", extname: ext, **options) 28 | end 29 | end 30 | 31 | private 32 | def assets_load_path(ext: '.js', suffix: nil, separator: '-') 33 | filename = "#{controller_path}/#{@_rendered_template}" 34 | filename = [filename, suffix].join(separator) if suffix 35 | filename = "#{filename}#{ext}" 36 | 37 | #pathname = Pathname.new(@_rendered_template_path) 38 | #asset_name = pathname.without_extname.sub_ext ext 39 | r = RailsVite.manifest.find(filename) 40 | 41 | if r 42 | [r['file'], ext] 43 | else 44 | [] 45 | end 46 | end 47 | 48 | def dev_host? 49 | Rails.env.development? && !RailsVite.instance.manifest.exist? 50 | end 51 | 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rails_vite/exporter.rb: -------------------------------------------------------------------------------- 1 | require_relative 'yaml' 2 | require 'active_support/core_ext/pathname/existence' 3 | 4 | module RailsVite 5 | module Exporter 6 | extend self 7 | 8 | def export 9 | vite = Yaml.new(template: 'config/vite_template.yml', export: 'config/vite.yml') 10 | 11 | vite.append 'entry_paths', Rails.root.join('app/views').to_s 12 | vite.append 'entry_paths', Rails.root.join('app/entrypoints').to_s 13 | vite.add 'alias', { 'rails' => Rails.root.join('app/assets').to_s } 14 | Rails::Engine.subclasses.each do |engine| 15 | asset_root = engine.root.join('app/assets') 16 | if asset_root.directory? 17 | vite.add 'alias', { "#{engine.engine_name}_ui" => asset_root.to_s } 18 | end 19 | 20 | vue_root = engine.root.join('app/vue') 21 | if vue_root.directory? 22 | vite.add 'alias', { "#{engine.engine_name}_vue" => vue_root.to_s } 23 | end 24 | 25 | view_root = engine.root.join('app/views') 26 | if view_root.directory? 27 | vite.append 'entry_paths', view_root.to_s 28 | vite.add 'alias', { "#{engine.engine_name}_view" => view_root.to_s } 29 | end 30 | 31 | entrypoint_root = engine.root.join('app/assets', 'entrypoints') 32 | if entrypoint_root.directory? 33 | vite.append 'entry_paths', entrypoint_root.to_s 34 | end 35 | 36 | # 为每个 engine 运行 yarn install 37 | if engine.root.join('yarn.lock').exist? 38 | Dir.chdir engine.root do 39 | $stdout.puts "\e[35minstall\e[0m #{engine.root}" 40 | system 'yarn install' 41 | end 42 | end 43 | end 44 | 45 | vite.dump 46 | end 47 | 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rails_vite/commands.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class Commands 3 | delegate :config, :compiler, :manifest, :logger, to: :@rails_vite 4 | 5 | def initialize(rails_vite) 6 | @rails_vite = rails_vite 7 | end 8 | 9 | # Cleanup old assets in the compile directory. By default it will 10 | # keep the latest version, 2 backups created within the past hour. 11 | # 12 | # Examples 13 | # 14 | # To force only 1 backup to be kept, set count=1 and age=0. 15 | # 16 | # To only keep files created within the last 10 minutes, set count=0 and 17 | # age=600. 18 | # 19 | def clean(count = 2, age = 3600) 20 | if config.public_output_path.exist? && config.public_manifest_path.exist? 21 | versions 22 | .sort 23 | .reverse 24 | .each_with_index 25 | .drop_while do |(mtime, _), index| 26 | max_age = [0, Time.now - Time.at(mtime)].max 27 | max_age < age || index < count 28 | end 29 | .each do |(_, files), index| 30 | files.each do |file| 31 | if File.file?(file) 32 | File.delete(file) 33 | logger.info "Removed #{file}" 34 | end 35 | end 36 | end 37 | end 38 | 39 | true 40 | end 41 | 42 | def clobber 43 | config.public_output_path.rmtree if config.public_output_path.exist? 44 | config.public_manifest_path.rmtree if config.public_manifest_path.exist? 45 | end 46 | 47 | def bootstrap 48 | manifest.refresh 49 | end 50 | 51 | def compile 52 | compiler.compile.tap do |success| 53 | manifest.refresh if success 54 | end 55 | end 56 | 57 | private 58 | def versions 59 | all_files = Dir.glob("#{config.public_output_path}/**/*") 60 | manifest_config = Dir.glob("#{config.public_manifest_path}*") 61 | 62 | packs = all_files - manifest_config - current_version 63 | packs.reject { |file| File.directory?(file) }.group_by { |file| File.mtime(file).utc.to_i } 64 | end 65 | 66 | def current_version 67 | packs = manifest.refresh.values.map do |value| 68 | value = value['src'] if value.is_a?(Hash) 69 | next unless value.is_a?(String) 70 | 71 | File.join(config.root_path, 'public', "#{value}*") 72 | end.compact 73 | 74 | Dir.glob(packs).uniq 75 | end 76 | 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/rails_vite/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'active_support/core_ext/hash/keys' 3 | require 'active_support/core_ext/hash/indifferent_access' 4 | 5 | module RailsVite 6 | class Configuration 7 | attr_reader :root_path, :config_path, :env 8 | 9 | def initialize(root_path:, config_path:, env:) 10 | @root_path = root_path 11 | @config_path = config_path 12 | @env = env 13 | end 14 | 15 | def server 16 | fetch(:server) 17 | end 18 | 19 | def host 20 | "#{server[:host]}:#{server[:port]}" 21 | end 22 | 23 | def compile? 24 | fetch(:compile) 25 | end 26 | 27 | def source_path 28 | root_path.join(fetch(:root_path)) 29 | end 30 | 31 | def additional_paths 32 | fetch(:additional_paths) 33 | end 34 | 35 | def source_entry_path 36 | source_path.join(fetch(:source_entry_path)) 37 | end 38 | 39 | def public_path 40 | root_path.join(fetch(:public_root_path)) 41 | end 42 | 43 | def public_output_path 44 | public_path.join(data.dig(:build, :assetsDir)) 45 | end 46 | 47 | def public_manifest_path 48 | public_path.join('manifest.json') 49 | end 50 | 51 | def cache_manifest? 52 | fetch(:cache_manifest) 53 | end 54 | 55 | def cache_path 56 | root_path.join(fetch(:cache_path)) 57 | end 58 | 59 | def webpack_compile_output? 60 | fetch(:webpack_compile_output) 61 | end 62 | 63 | def fetch(key) 64 | data.fetch(key, defaults[key]) 65 | end 66 | 67 | def data 68 | @data ||= load 69 | end 70 | 71 | def load 72 | config = begin 73 | YAML.load_file(config_path.to_s, aliases: true) 74 | rescue ArgumentError 75 | YAML.load_file(config_path.to_s) 76 | end 77 | config[env].deep_symbolize_keys 78 | rescue Errno::ENOENT => e 79 | raise "RailsVite configuration file not found #{config_path}. " \ 80 | "Please run rails rails_vite:install " \ 81 | "Error: #{e.message}" 82 | 83 | rescue Psych::SyntaxError => e 84 | raise "YAML syntax error occurred while parsing #{config_path}. " \ 85 | "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ 86 | "Error: #{e.message}" 87 | end 88 | 89 | def defaults 90 | @defaults ||= begin 91 | path = File.expand_path('../../config/vite_default.yml', __dir__) 92 | config = begin 93 | YAML.load_file(path, aliases: true) 94 | rescue ArgumentError 95 | YAML.load_file(path) 96 | end 97 | HashWithIndifferentAccess.new(config[env]) 98 | end 99 | end 100 | 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/rails_vite/yaml.rb: -------------------------------------------------------------------------------- 1 | module RailsVite 2 | class Yaml 3 | attr_reader :content 4 | # uses config/viter_template.yml in rails_ui engine as default, 5 | # config/viter_template.yml in Rails project will override this. 6 | def initialize(template:, export:) 7 | template_path = (Rails.root + template).existence || Engine.root + template 8 | export_path = Rails.root + export 9 | 10 | @yaml = YAML.parse_stream File.read(template_path) 11 | @content = @yaml.children[0].children[0].children 12 | @parsed = @yaml.to_ruby[0] 13 | @io = File.new(export_path, 'w+') 14 | end 15 | 16 | def dump 17 | @yaml.yaml @io 18 | @io.fsync 19 | @io.close 20 | end 21 | 22 | def append(env = 'default', key, value) 23 | env_content, index = xx(env, key) 24 | if index 25 | value_content = env_content[index] 26 | else 27 | return 28 | end 29 | 30 | return if value_content.children.find { |i| i.value == value } 31 | 32 | if value_content.is_a?(Psych::Nodes::Sequence) 33 | value_content.style = 1 # block style 34 | value_content.children << Psych::Nodes::Scalar.new(value) 35 | end 36 | 37 | value_content 38 | end 39 | 40 | def add(env = 'default', key, value) 41 | env_content, index = xx(env, key) 42 | if index 43 | value_content = env_content[index] 44 | else 45 | return 46 | end 47 | 48 | if value_content.is_a?(Psych::Nodes::Sequence) # 数组 49 | value_content.style = 1 # block style 50 | value_content.children << Psych::Nodes::Scalar.new(value) 51 | elsif value_content.is_a?(Psych::Nodes::Mapping) && value.is_a?(Hash) # 有下级内容 52 | value.each do |item, val| 53 | value_content.children << Psych::Nodes::Scalar.new(item) 54 | value_content.children << Psych::Nodes::Scalar.new(val) 55 | end 56 | elsif value_content.is_a?(Psych::Nodes::Scalar) && value.is_a?(Hash) 57 | value_content.children = [] 58 | value.each do |item, val| 59 | value_content.children << Psych::Nodes::Scalar.new(item) 60 | value_content.children << Psych::Nodes::Scalar.new(val) 61 | end 62 | end 63 | 64 | value_content 65 | end 66 | 67 | def set(env = 'default', key, value) 68 | env_content, index = xx(env, key) 69 | if index 70 | value_content = env_content[index] 71 | value_content.value = value 72 | elsif key && value 73 | env_content << Psych::Nodes::Scalar.new(key) 74 | env_content << Psych::Nodes::Scalar.new(value) 75 | end 76 | end 77 | 78 | def xx(env, key) 79 | env_index = @content.find_index { |i| i.is_a?(Psych::Nodes::Scalar) && i.value == env } 80 | env_content = @content[env_index + 1].children 81 | value_index = env_content.find_index { |i| i.is_a?(Psych::Nodes::Scalar) && i.value == key } 82 | if value_index 83 | index = value_index + 1 84 | else 85 | index = nil 86 | end 87 | 88 | [env_content, index] 89 | end 90 | 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/rails_vite/compiler.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | require 'digest/sha1' 3 | 4 | module RailsVite 5 | class RailsVite::Compiler 6 | 7 | # Additional environment variables that the compiler is being run with 8 | # RailsVite::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key' 9 | cattr_accessor(:env) { {} } 10 | 11 | delegate :config, :logger, to: :rails_vite 12 | attr_reader :rails_vite 13 | 14 | def initialize(rails_vite) 15 | @rails_vite = rails_vite 16 | end 17 | 18 | def compile 19 | if true #stale? 20 | run_vite.tap do |success| 21 | #record_compilation_digest 22 | end 23 | else 24 | true 25 | end 26 | end 27 | 28 | # Returns true if all the compiled packs are up to date with the underlying asset files. 29 | def fresh? 30 | last_compilation_digest&.== watched_files_digest 31 | end 32 | 33 | # Returns true if the compiled packs are out of date with the underlying asset files. 34 | def stale? 35 | !fresh? 36 | end 37 | 38 | private 39 | def last_compilation_digest 40 | compilation_digest_path.read if compilation_digest_path.exist? && config.public_manifest_path.exist? 41 | rescue Errno::ENOENT, Errno::ENOTDIR 42 | end 43 | 44 | def watched_files_digest 45 | warn "Webpacker::Compiler.watched_paths has been deprecated. Set additional_paths in rails_vite.yml instead." unless watched_paths.empty? 46 | Dir.chdir File.expand_path(config.root_path) do 47 | files = Dir[*default_watched_paths, *watched_paths].reject { |f| File.directory?(f) } 48 | file_ids = files.sort.map { |f| "#{File.basename(f)}/#{Digest::SHA1.file(f).hexdigest}" } 49 | Digest::SHA1.hexdigest(file_ids.join("/")) 50 | end 51 | end 52 | 53 | def record_compilation_digest 54 | config.cache_path.mkpath 55 | compilation_digest_path.write(watched_files_digest) 56 | end 57 | 58 | def ruby_runner 59 | bin_webpack_path = config.root_path.join('bin/vite') 60 | first_line = File.readlines(bin_webpack_path).first.chomp 61 | /ruby/.match?(first_line) ? RbConfig.ruby : '' 62 | end 63 | 64 | def run_vite 65 | logger.info 'RailsVite Compiling...' 66 | 67 | stdout, stderr, status = Open3.capture3( 68 | vite_env, 69 | "#{ruby_runner} ./bin/vite build", 70 | chdir: File.expand_path(config.root_path) 71 | ) 72 | 73 | if status.success? 74 | logger.info "Compiled all packs in #{config.root_path}" 75 | logger.error "#{stderr}" unless stderr.empty? 76 | logger.info stdout 77 | else 78 | non_empty_streams = [stdout, stderr].delete_if(&:empty?) 79 | logger.error "Compilation failed:\n#{non_empty_streams.join("\n\n")}" 80 | end 81 | 82 | status.success? 83 | end 84 | 85 | def default_watched_paths 86 | [ 87 | *config.additional_paths, 88 | "#{config.source_path}/**/*", 89 | 'yarn.lock', 90 | 'package.json', 91 | 'config/vite/**/*' 92 | ].freeze 93 | end 94 | 95 | def compilation_digest_path 96 | config.cache_path.join("last-compilation-digest-#{rails_vite.env}") 97 | end 98 | 99 | def vite_env 100 | return env unless defined?(ActionController::Base) 101 | 102 | env.merge( 103 | 'VITER_ASSET_HOST' => ENV.fetch('VITER_ASSET_HOST', ActionController::Base.helpers.compute_asset_host), 104 | 'VITER_RELATIVE_URL_ROOT' => ENV.fetch('VITER_RELATIVE_URL_ROOT', ActionController::Base.relative_url_root), 105 | 'VITER_CONFIG' => rails_vite.config_path.to_s 106 | ) 107 | end 108 | 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /app/helpers/vite/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Public: Allows to render HTML tags for scripts and styles processed by Vite. 4 | 5 | module Vite 6 | module ApplicationHelper 7 | 8 | def image_vite_tag(name, **options) 9 | if vite_manifest.exist? 10 | r = compute_manifest_name(name, type: :image) 11 | mani = vite_manifest.find(r) 12 | if mani 13 | image_tag("/#{mani['assets'][0]}", **options) 14 | end 15 | else 16 | image_tag(name, **options) 17 | end 18 | end 19 | 20 | # Public: Resolves the path for the specified Vite asset. 21 | # 22 | # Example: 23 | # <%= vite_asset_path 'calendar.css' %> # => "/vite/assets/calendar-1016838bab065ae1e122.css" 24 | def asset_vite_path(name, **options) 25 | asset_path name, options 26 | end 27 | 28 | def image_vite_path(name, **options) 29 | if vite_manifest.exist? 30 | r = compute_manifest_name(name, type: :image) 31 | mani = vite_manifest.find(r) 32 | if mani 33 | image_path("/#{mani['assets'][0]}", **options) 34 | end 35 | else 36 | image_path(name, options) 37 | end 38 | end 39 | 40 | # Public: Renders a