Rails 6/Webpacker not serving static assets to React - ruby-on-rails

I have a React frontend using Rails 6 as an API. Webpacker is configured to package image files in app/javascript/media to app/javascript/packs. As far as I can tell, Webpack is creating the packs correctly; the packs folder contains my bundle.js, bundle.js map, and the .svg file bundled by webpack. Bundle.js is running fine, but when I try and import the image in a component, I'm getting a routing error stating:
ActionController::RoutingError (No route matches [GET] "/img.svg")
All of the questions I've seen about this mention that static assets don't get served by Rails in production mode without further configuration, but I have Rails in development mode and webpack-dev-server running.
Here's my webpack.config.js:
const path = require('path');
module.exports = {
context: __dirname,
entry: './frontend/app_name.jsx',
output: {
path: path.resolve(__dirname, 'app', 'javascript', 'packs'),
publicPath: path.resolve(__dirname, 'app', 'javascript', 'packs'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.js', '.jsx', '*']
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
query: {
presets: ['#babel/env', '#babel/react']
}
},
},
{
test: /\.(png|svg)?$/,
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
]
},
devtool: 'source-map'
};
Update: My routes.rb contains:
Rails.application.routes.draw do
root 'static#index'
namespace :api, defaults: { format: :json } do
resources :search, only: [:index]
end
end
and my static controller is
class StaticController < ApplicationController
def index
render :index
end
end
Should requests for static assets compiled by webpacker even be making it to the backend in a one page app?

Related

How to add ActionCable to a ruby on rails engine?

I am working in a project which has 3 separate parts - a ruby on rails engine, a ruby on rails application, and a frontend react application. I need to introduce websockets / ActionCable in the engine, and am not having any luck getting ActionCable running.
I have the following relevant files in the engine:
app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
app/channels/fromserver_channel.rb
class FromServerChannel < ApplicationCable::Channel
def subscribed
stream_from "fromClient"
end
def receive(data)
puts '----------'
puts data
ActionCable.server.broadcast "fromServer", data
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
config/environments/development.rb
require 'action_cable/engine'
Rails.application.configure do
config.action_cable.disable_request_forgery_protection = true
#specify the socket URI for ActionCable
config.action_cable.url = "/cable"
config.action_cable.allowed_request_origins = [/http:\/\/*/, /https:\/\/*/]
end
config/cable.rb
development:
adapter: async
test:
adapter: async
production:
adapter: postgresql
config/routes.rb
require 'action_cable/engine'
RoundaboutEngine::Engine.routes.draw do
mount ActionCable.server => '/cable'
# mount RoundaboutEngine::Engine => "/cable"
scope controller: 'round_about' do
get 'organizer' => :show
end
end
Rails.application.routes.send(:draw) do
mount RoundaboutEngine::Engine => '/' if RoundaboutEngine.draw_routes_in_host_app
end
The front end looks like this:
import React, { useState } from 'react';
import channel from './cable';
const RoundAboutView = () => {
const [displayText, setDisplayText] = useState('');
const sendUpdate = (arg) => {
channel.sending(arg);
};
return (
...
Text:
{` ${displayText}`}
...
);
};
export default RoundAboutView;
import ActionCable from 'actioncable';
// const cable = ActionCable.createConsumer('wss://localhost:9292/cable');
const cable = ActionCable.createConsumer(`ws://${window.location.host}/cable`);
const channel = cable.subscriptions.create(
{ channel: 'FromServerChannel' },
{
connected() { },
disconnected() {},
received: (data) => {
console.log(data);
},
sending(data) {
this.perform('fromClient', { data });
},
},
);
export default channel;
The problem I am having is that Chrome says the websocket can't connect (WebSocket connection to 'ws://localhost:9292/cable' failed). In the terminal, when in routes.rb I mount /cable with ActionCable.server, I see the below:
"exception":{
"type":"NoMethodError",
"message":"undefined method `info' for nil:NilClass",
"stack_trace":"NoMethodError(undefined method `info' for nil:NilClass)\n actioncable (5.2.6) lib/action_cable/connection/tagged_logger_proxy.rb:38:in `block in log'\n actioncable (5.2.6)
When I don't do that and instead mount /cable with the engine, I get an error that the route GET /cable doesn't exist.
I'm not able to move past this point and I'm not sure what I'm doing wrong or missing in the code. I've seen the example at https://github.com/palkan/engine-cable-app and wasn't able to take that example and make it work in my code. Can anyone help me?

Angular - Issue with Routing Views Coming From .NET MVC 5.0

I am having issues setting up my Angular project with .NET MVC 5.0. I am not sure what's wrong with the below code. When I run the application, unexpectedly to me, app shows the template set in app-component.ts and not login
const appRoutes: Routes = [
{ path: '', redirectTo: 'login', pathMatch:'full' },
{ path: 'login', component: LoginComponent },
// otherwise redirect to home
{ path: '**', redirectTo: 'login' }
];
To test out the things, and ignore MVC controller/view routing for a second, I also tried creating html file inside in my login folder,
#Component({
moduleId: module.id,
templateUrl: 'login-component.html' -- This was initially /Public/Login -Path to the MVC controller
})
Project is shared on a github project here
https://github.com/GAK-MPRO/AngularMVCStarter/tree/Master/A2Rnd
My question is.. what do I need to do to route my views using MVC routing with views rendered by calling controllers.
Change the order in which your routes are defined. The default routes should always be at the end of the route list:
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: 'login', pathMatch:'full' },
// otherwise redirect to home
{ path: '**', redirectTo: 'login', pathMatch:'full' }
];
I just looked at your code on git hub. The bootstrap module is trying to bootstrap appcomponent and the appcomponent does not have an router-outlet tag. Edit the template in the app.component.ts file to include
<router-outlet></router-outlet>
and it should show you both app component and login components html content.
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: '<h1>Hello {{name}}</h1><br/><router-outlet></router-outlet>',
})
export class AppComponent { name = 'Angular'; }

How to get webpack2 and underscore-template loader + babel to work without getting "Module build failed: SyntaxError: 'with' in strict mode (5:0)"

I have a underscore template loader in my webpack2 config that is transpiled with babel. It fails at compile time because with is used in the code compiled code. Here is the relevant part in my loaders in webpack.config.js:
I have this section under loaders:
{
test: /\.html$/,
use: [
{
loader: 'babel-loader',
query: {
presets: [
['es2015', { modules: false }],
'es2016',
'es2017',
'stage-3',
],
},
},
{
loader: 'ejs-loader',
},
],
};
This is what I want and I get:
ERROR in ./src/table/row.html
Module build failed: SyntaxError: 'with' in strict mode (5:0)
3 | var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
4 | function print() { __p += __j.call(arguments, '') }
> 5 | with (obj) {
| ^
6 |
7 | _.each(tableKeys, (k) => { ;
8 | __p += '\n <td>' +
If I remove the babel part completely it works but with ES6 code not transpiled:
{
test: /\.html$/,
use: [
{
loader: 'ejs-loader',
},
],
};
I have also seen this question about removing strict mode and have tried several things related to es2015 applying strict. I think I have tried every solution in that question including hotpatching workaround and I still get the same error. In the end i tried this:
{
test: /\.html$/,
use: [
{
loader: 'babel-loader',
query: {
presets: [
],
},
},
{
loader: 'ejs-loader',
},
],
};
I though this should do the same as without the bable pass, but I get the same error here. Somehow without any presets I get the same error.
EDIT
I have also tried to work around it by passing variable in query and I have made that work with ejs-loader, however I'm not looking for a solution where all the templates need changing.
I have made a repository which illustrates the problem. The master branch has babel-loader commented out and works with with while the transpile branch will have compile errors even though { modules: false } is passed and I have a branch called transpile-no-presets where all presets in package.json is removed and the error is still showing.
By default Underscore .template put your data in the scope using a with statement. See Underscore docs.
I think the cleanest solution is to instruct your ejs-loader to not compile to with statements but to use a temporary variable instead:
{
loader: 'ejs-loader?variable=data',
},
...and change your templates to reference the temporary variable.
From:
<ul>
<% _.each(test, (n) => { %>
<li>This is line number <%- n %></li>
<% }); %>
</ul>
to:
<ul>
<% _.each(data.test, (n) => { %>
<li>This is line number <%- n %></li>
<% }); %>
</ul>
Use version 1.0 of underscore-template-loader instead.

Ember cannot display JSON from Rails

I have an Ember CLI app that uses Rails as a backend. The code appears to be fine, and if I visit localhost:3000, it displays the JSON output just fine. However, Ember does not display this data.
Here is what I have so far:
// ember/app/adapters/application.js
import DS from "ember-data";
export default DS.ActiveModelAdapter.extend({
namespace: 'api'
});
// ember/app/controllers/data_keys.js
export default Ember.ArrayController.extend({
sortProperties: ['name']
});
// ember/app/models/data_key.js
export default DS.Model.extend({
name: DS.attr('string')
});
// ember/app/routes/data_keys/index.js
export default Ember.Route.extend({
controllerName: 'data_keys',
model: function() {
return this.store.all('data_key');
}
});
// rails/app/controllers/api/data_keys_controller.rb
class Api::DataKeysController < ApplicationController
def index
render json: DataKey.all
end
end
// rails/app/serializers/data_key_serializer.rb
class DataKeySerializer < ActiveModel::Serializer
attributes :id, :name
end
// rails/config/routes.rb
Rails.application.routes.draw do
namespace :api do
resources :data_keys
end
end
To run the app, I run the Rails side with rails s, then in another terminal tab I run ember server --proxy http://localhost:3000.
Is there something obvious that I'm missing? I know that Ember CLI version 0.0.39 had proxy uses, but I'm using Ember CLI 0.0.40.
ember -v
version: 0.0.40
node: 0.10.28
npm: 1.4.21
Here's an example of the JSON returned by the server:
{
"data_keys": [{
"id": 1,
"name": "foo"
}, {
"id": 2,
"name": "bar"
}]
}
I think that when you use DS.Store#all you're only filtering the records that are already in the store. You'll want to use DS.Store#find in order to get the records from the server.
export default Ember.Route.extend({
controllerName: 'data_keys',
model: function() {
return this.store.find('data_key');
}
});
Also, your ember files should use dashes rather than underscores.

AngularJS routes can't be recognized on manual refresh

Hi I followed a tutorial: http://omarriott.com/aux/angularjs-html5-routing-rails/
Here's my manual.js.coffee.erb
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
#= require_tree ./templates
#Bento = angular.module('bento', ['ngRoute', 'ngResource', 'ngSanitize', 'ui.bootstrap', 'infinite-scroll'])
# Pre-cache templates
Bento.run ($window, $templateCache) ->
templates = $window.JST
for fileName of templates
fileContent = templates[fileName]
$templateCache.put(fileName, fileContent)
# Note that we're passing the function fileContent,
# and not the object returned by its invocation.
#= require_tree ./controllers
#= require_tree ./services
#= require_tree ./directives
My routes.js.coffee.erb
#Bento.config ["$routeProvider", ($routeProvider) ->
$routeProvider.when("/",
controller: "ActivitiesController"
templateUrl: "<%= asset_path('templates/user/activities.html.haml') %>"
redirectTo: (current, path, search) ->
if search.goto
"/" + search.goto
else
"/"
).when("/projects/activities",
controller: "ProjectAllActivitiesController"
templateUrl: "<%= asset_path('templates/projects/activities.html') %>"
redirectTo: "/example"
).when("/projects/:id",
controller: "ProjectCtrl"
templateUrl: "<%= asset_path('templates/projects/list.html') %>"
redirectTo: (current, path, search) ->
if search.goto
"/projects/:id" + search.goto
else
"/projects/:id"
).otherwise redirectTo: "/"
]
And lines that I added to my config/routes.rb file
match "api" => proc { [404, {}, ['Invalid API endpoint']] }, via: [:post]
match "api/*path" => proc { [404, {}, ['Invalid API endpoint']] }, via: [:post]
match "/*path" => redirect("/?goto=%{path}"), via: [:post]
Since I'm using rails 4, I need to add a via
# sign is adding whenever I refresh my root path localhost:3000/ but it's not working in nested URLs like localhost:3000/projects/1 it's still reading my routes for RAILS.
PS: Whenever i refresh localhost:3000/projects/activities for example, it's still reading my rails routes instead of going to localhost:3000/example.

Resources