Rails Webpacker does not compile subdirectory - ruby-on-rails

EDIT: Solution: make sure you spell the class-methods correctly. My error stemmed from typing contructor() within the class (please refer to the source-code of SocialShareModal.js.
Also, make sure your linter in your editor of choice works correctly! Mine did not. It would have spared me hours if it actually did :-)
I am running a Rails-application (ruby v 2.6.2 / Rails v 6.0.2) using webpacker. My JavaScript has been working like a charm, up until I tried putting component-related JS into a dedicated sub-directory of my app/javascript-folder.
This is what my JS-file-tree looks like:
javascript
├──channels
├──custom
│ ├──components (new & not working)
│ ├──config
│ ├──helpers (these are working somehow)
│ └──pages
├──config
└──packs
In application.js I import a custom Router.js, initialize it with my custom routes to then, on various subpages, initialize my custom JS-classes. It all worked so far (and continues to) with classes which live in the helpers-folder, however the classes which live in the new components-folder won't work. I am unsure if they are even picked up and compiled by webpack.
application.js:
import routes from '../custom/config/routes'
import Router from '../custom/Router'
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require('channels')
...
class myApp {
constructor() {
this._initRouter()
}
/**
* Initializes the router and its routes
* #private
*/
_initRouter () {
this._router = new Router(routes)
}
}
document.addEventListener('turbolinks:load', function() {
window.myApp = new myApp()
})
routes.js:
import Page from '../pages/page'
// Frontend
import Root from '../pages/frontend/root'
import SignInPage from '../pages/frontend/signInPage'
// Dashboard => Admin
import AdminAccountsEditPage from '../pages/dashboard/admin/accounts/edit'
// Dashboard => User
import WelcomePage from '../pages/dashboard/user/welcomePage'
export default [
// Frontend
['', Root],
['accounts/sign_in', SignInPage],
// Dashboard => Admin
['admin/accounts/(.*)', AdminAccountsEditPage],
// Dashboard => User
['dashboard/willkommen', WelcomePage],
// Catch all for when there is no exact match:
['(.*)', Page]
]
Router.js:
/* global location */
export default class Router {
constructor(routes) {
this.routes = routes
this.handleRoute()
}
/**
* Checks if there's a javascript for the current route, requires the class and
* instantiates it
* #private
*/
handleRoute() {
let { pathname } = location
// Remove leading and trailing slashes
pathname = pathname.replace(/^\/|\/$/g, '')
// Go through routes and check which one matches
for (let i = 0; i < this.routes.length; i++) {
const [route, PageClass] = this.routes[i]
const regexp = new RegExp(`^${route}$`, 'i')
if (route === true || regexp.test(pathname)) {
this.currentPage = new PageClass()
break
}
}
}
}
Page.js:
import tippy from 'tippy.js'
import 'tippy.js/dist/tippy.css'
import FlashMessageHelper from '../helpers/FlashMessageHelper'
import AddToWishlistHelper from '../helpers/AddToWishlistHelper'
import SocialShareModal from '../components/SocialShareModal' // importing it
export default class Page {
constructor() {
new tippy('[data-tippy-content]')
new FlashMessageHelper() // working
new AddToWishlistHelper() // working
new SocialShareModal() // NOT working (not initializing)
}
}
SocialShareModal.js
export default class SocialShareModal {
get modalSelector() { return '.modal' }
get triggerModalSelector() { return '.js-trigger-modal' }
get copyToClipBoardButtonSelector() { return '.js-copy-to-clipboard' }
contructor() { // As you can see, the error resided here
console.log('SocialShareModal constructor called')
this.init()
}
init() {
let modalButton = document.querySelector(this.triggerModalSelector)
modal.addEventListener('click', handleModalTrigger)
window.addEventListener('scroll', this.handleTestScroll)
}
handleModalTrigger() {
let modal = document.querySelector(this.modalSelector)
modal.classList.add('is-active')
}
}
I've done lots of reading, but can't seem to figure out the issue, as I'm not super-comfortable with webpack. Any suggestions on how to solve this?
Edit: added source-code for application.js, routes.js, Router.js, Page.js & SocialShareModal.js to provide more context.

Related

How to resolve Error: Unable to resolve specifier 'stimulus' error

The browser console complains with:
Failed to register controller: test (controllers/test_controller) Error: Unable to resolve specifier 'stimulus'
[the highest line of source references points to where the error is triggere
if (!(name in registeredControllers)) {
importShim(path, 'http://localhost:3000/assets/stimulus-loading-1fc59770fb1654500044afd3f5f6d7d00800e5be36746d55b94a2963a7a228aa.js')
.then(module => registerController(name, module, application))
.catch(error => console.error(`Failed to register controller: ${name} (${path})`, error))
}
the last line points to the source of the problem:
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from /*"#hotwired/stimulus-loading"*/'blob:http://localhost:3000/cf2bed28-84d1-496d-a453-7a2818e07002'
eagerLoadControllersFrom("controllers", application)
So clearly the app/javascript/controllers/test-controller.js is not firing properly. Its first line calling
import { Controller } from "stimulus"
Which I find odd, as app/javascript/controllers/application.js contains the stock code
How does one resolve the specifier 'stimulus'?
import { Application } from "#hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }
have you tried this way? On app/javascript/controllers/test-controller.js write this way
import { Controller } from "#hotwired/stimulus";

Using ActionText without webpack

I try to implement ActionTest with old way asset pipeline (without Webpack) on rails 6
Almost all is good, except loading of #rails/actiontext
in my application.js I've
//= require trix
//= require #rails/actiontext
The riche editor appear, I can change bold/italic text, but can't add image (not uploaded)
I've an JS error : Uncaught SyntaxError: Cannot use import statement outside a module
on line : import { AttachmentUpload } from "./attachment_upload" from attachment_uplaod.js in actiontext.
Any way to achieve this without webpack?
thanks
I don't know what will be the official way, but I did it this way as I'm waiting for an updated install generator:
Prerequisites
hotwire-rails
CSS
Copy the CSS file in your asset pipeline (https://github.com/basecamp/trix/tree/main/dist)
JS Libraries
In app/assets/javascripts/libraries create these two files
Updated content may be found on https://www.skypack.dev
// app/assets/javascripts/libraries/actiontext#6.1.4.js
export * from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports/optimized/#rails/actiontext.js';
export { default } from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports,min/optimized/#rails/actiontext.js';
// app/assets/javascripts/libraries/trix#1.3.1.js
export * from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports/optimized/trix.js';
export { default } from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports,min/optimized/trix.js';
Import through Stimulus
In app/assets/javascripts/controllers create this file
//app/assets/javascripts/controllers/trix_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
import("actiontext").catch(err => null)
import("trix").catch(err => null)
}
}
On pages where trix/action_text should be loaded, add a data-controller="trix" to the relevant div
And voilà !
https://github.com/rails/rails/issues/41221#issuecomment-871853505
Got Action Text working by copying the actiontext scripts into a custom file, and removing the imports and exports.
And second, you will need to require activestorage in your application.js to make use of DirectUpload.
application.js
//= require trix
//=# require #rails/actiontext
//= require activestorage
//= require actiontext
actiontext.js
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/attachment_upload.js
class AttachmentUpload {
constructor(attachment, element) {
this.attachment = attachment;
this.element = element;
// Requires `require activestorage` in application.js
this.directUpload = new ActiveStorage.DirectUpload(
attachment.file,
this.directUploadUrl,
this
);
}
start() {
this.directUpload.create(this.directUploadDidComplete.bind(this));
}
directUploadWillStoreFileWithXHR(xhr) {
xhr.upload.addEventListener("progress", event => {
const progress = (event.loaded / event.total) * 100;
this.attachment.setUploadProgress(progress);
});
}
directUploadDidComplete(error, attributes) {
if (error) {
throw new Error(`Direct upload failed: ${error}`);
}
this.attachment.setAttributes({
sgid: attributes.attachable_sgid,
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
});
}
createBlobUrl(signedId, filename) {
return this.blobUrlTemplate
.replace(":signed_id", signedId)
.replace(":filename", encodeURIComponent(filename));
}
get directUploadUrl() {
return this.element.dataset.directUploadUrl;
}
get blobUrlTemplate() {
return this.element.dataset.blobUrlTemplate;
}
}
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/index.js
addEventListener("trix-attachment-add", event => {
const { attachment, target } = event;
if (attachment.file) {
const upload = new AttachmentUpload(attachment, target);
upload.start();
}
});
This still uses ES6 syntax, so if you want to support older browsers and aren't using Babel, you might want to rewrite or transpile this to ES5.

How to import library-generated IIEF style JavaScript file?

Is there any way to import some library-generated code which does not have export vars? Which module options should be set in tsconfig.json file?
Documents
about "Module" system in TypeScript
Environment
typescript #2.3.2
js-routes #1.3.3
Example
js-routes generate a code like below based on routes.rb:
/*
File generated by js-routes 1.3.3
Based on Rails routes of MyApplication
*/
(function() {
...
return root.Routes;
};
if (typeof define === "function" && define.amd) {
define([], function() {
return createGlobalJsRoutesObject();
});
} else {
createGlobalJsRoutesObject();
}
}).call(this);
If you import in 'module=es2015' style,
import { Routes } from '../path/to//generated/js_routes';
tsc warns:
[ts] File '/path/to/client/generated/js_routes.js' is not a module.
You can try to require the file, and see what's the output of that:
import Routes = require('../path/to//generated/js_routes');

Vue Router Webpack Dot in Params

I'm creating an app with NodeJS/Express for the back and VueJS for the Front using Vue Cli and webpack.
I'd like to know if there is a way to allow dot in params for my routes.
Here is what I get when i try with no config
Cannot GET /t/firstname.lastname
Here is my /src/main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import VueAutosize from 'vue-autosize'
import Main from './components/Main.vue'
import Signin from './components/Signin.vue'
// We want to apply VueResource and VueRouter
// to our Vue instance
Vue.use(VueRouter)
Vue.use(VueResource)
Vue.use(VueAutosize)
const router = new VueRouter({
history: true
})
// Pointing routes to the components they should use
router.map({
'/t/:person': {
component: Main
},
'/signin': {
component: Signin
}
})
router.beforeEach(function (transition) {
if (transition.to.path === '/signin' && window.localStorage.length !== 0) {
transition.redirect('/')
} else if (transition.to.path === '/' && window.localStorage.length === 0) {
transition.redirect('/signin')
} else {
transition.next()
}
})
// Any invalid route will redirect to home
router.redirect({
'*': '/404'
})
router.start(App, '#app')
I was dealing with the same issue, even I'm late to the conversation maybe somebody will find useful the solution I found.
It appears to be webpack's fault.
If using vue-cli's webpack template you'll need to configure a proxy for the routes you need. For example in your case you'll need to add this to the config/index.js file:
...
dev: {
...
proxyTable: {
'/t/*.*': { // this will match all urls with dots after '/t/'
target: 'http://localhost:8080/', // send to webpack dev server
router: function (req) {
req.url = 'index.html' // Send to vue app
}
}
// Any other routes you need to bypass should go here.
}
...
},
...
This way webpack will proxy all requests to that url and don't treat these as files.
Add /something after params
eg: in your route
{
path: your-route/:paramname/something",
component: somecomponent,
}
After this, you should be able to access the route using this.$route.params.paramname
do below setting in webpack
devServer: {
historyApiFallback: {
disableDotRule: true
}
}

Request from Ember front to Rails back is not happening

I am implementing a front-end in ember 1.13 with a Rails back-end and having the following problem:
After the user is authenticated, I don't seem to be able to retrieve the user's record from the back-end. The browser debugger does not even show a request being made. This is code:
// app/services/session-user.js
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
loadCurrentUser() {
currentUser: {
var userId = this.get('user_id');
if (!Ember.isEmpty(userId)) {
return this.get('store').findAll('user', userId);
}
}
}
});
There is a login controller which handles the authentication. But the code for getting the data is in the applications's route:
// app/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
const { service } = Ember.inject;
export default Ember.Route.extend(ApplicationRouteMixin, {
sessionUser: service('session-user'),
beforeModel() {
if (this.session.isAuthenticated) {
return this._loadCurrentUser();
}
},
sessionAuthenticated() {
this._loadCurrentUser();
},
_loadCurrentUser() {
return this.get('sessionUser').loadCurrentUser();
},
});
For extra measure I am defining the session store:
// app/session-stores/application.js
import Adaptive from 'ember-simple-auth/session-stores/adaptive';
export default Adaptive.extend();
If there are files I should post, please let me know.
Any hints will be highly appreciated as I am rather new to ember. I have spent several hours researching without luck, as things seem to have changed quite a lot throughout versions.
Look at your service code.
var userId = this.get('user_id');
if (!Ember.isEmpty(userId)) {
return this.get('store').findAll('user', userId);
}
I don't see code that you provided in question where you setting up user_id variable. So if user_id not defined then if statement won't get executed because of !.

Resources