How to import library-generated IIEF style JavaScript file? - ruby-on-rails

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');

Related

How to use quasar framework in vuepress 2?

I'm now using vuepress2 with quasar 2.7.1 like this:
import { Quasar } from 'quasar';
export default defineClientAppEnhance(({ app, router, siteData }) => {
app.use(Quasar);
}
#import url(https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons|Material+Icons+Outlined);
#import 'quasar/src/css/variables.sass';
#import 'quasar/src/css/core/colors.sass';
.quasar-comp {
#import 'quasar/src/css/index.sass';
}
/* I wrap the custome component in class .quasar-comp
so that the style from quasar won't conflict with style from vuepress. */
but there are 2 issues:
The style from quasar cannot works on some components, like q-btn-dropdown or q-menu.
It works well on dev mode(npm run docs:dev), but failed to build(npm run docs:build).
✔ Compiling with vite - done
✖ Rendering pages - failed
TypeError: Cannot convert undefined or null to object
at Function.assign (<anonymous>)
at installQuasar (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:15228)
at Object.install (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:479348)
at Object.use (/Users/lxm/Documents/neo/leaneo-docs/node_modules/#vue/runtime-core/dist/runtime-core.cjs.prod.js:3393:28)
at /Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:3745:7
at createVueApp (/Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:4177:11)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:49:52
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/utils/lib/withSpinner.js:12:24
at async build (/Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:34:5)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/cli/lib/commands/build/createBuild.js:51:5
Is there a better way to make quasar and vuepress works together?
Here is the implementation in Vuepress 2 (beta) and Quasar with Typescript.
First, install Quasar into Vuepress with the new syntax and import all styles:
client.ts:
import { defineClientConfig } from "#vuepress/client";
import { Quasar } from "quasar";
import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({app, router, siteData}) {
app.use(Quasar);
},
setup() {},
rootComponents: [],
});
Then, ignore deprecation errors from sass if you're using vite.
config.ts
import { viteBundler } from '#vuepress/bundler-vite'
import { defineUserConfig } from '#vuepress/cli'
export default defineUserConfig({
// your config
...
bundler: viteBundler({
viteOptions: {
css: {
preprocessorOptions: {
scss: {
sassOptions: {
// ignore sass deprecation errors
quietDeps: true
}
}
}
}
},
}),
});
Hope it helps :)
Quasar needs some globales to be defined (ie if it's SSR). My following solution works ONYL on client-side so be sure to wrap your custom-quasar component in <ClientOnly> !
// .vuepress/config.ts
bundler: viteBundler({
viteOptions: {
define: {
__QUASAR_VERSION__: `'dev'`,
__QUASAR_SSR__: false,
__QUASAR_SSR_SERVER__: false,
__QUASAR_SSR_CLIENT__: false,
__QUASAR_SSR_PWA__: false
}
}
}),
// .vuepress/client.ts
import { defineClientConfig } from '#vuepress/client'
import Quasar from "quasar/src/install-quasar.js";
// optionally import your styles here
// import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({ app, router, siteData }) {
app.use(Quasar);
},
setup() {
},
rootComponents: [],
});
<!-- README.md -->
<ClientOnly>
<MyComponent />
</ClientOnly>

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.

Rails Webpacker does not compile subdirectory

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.

Rails Webpacker - How to access objects defined in webpack entry file from views [HTML file]

I have a Rails 6 application and using Webpacker for assets.
I have the following code in file app/javascript/packs/application.js :
export var Greeter = {
hello: function() {
console.log('hello');
}
}
And I have the following script in one of my view (HTML) file:
<script>
$(document).ready(function(){
Greeter.hello();
});
</script>
Note: I am using JQuery and it is working fine.
I am getting the following error:
Uncaught ReferenceError: Greeter is not defined.
How can we use libraryTarget and library to expose the bundled modules, so that it can be accessed from HTML files as well ?
Or, is there any other way of doing it using Rails Webpacker ?
Any help would be much appreciated!
To do this without directly mutating the window object in your application code, you'll want to export Greeter as a named export from your application.js pack and extend the Webpack config output to designate the library name and target var (or window will also work).
// config/webpack/environment.js
environment.config.merge({
output: {
library: ['Packs', '[name]'], // exports to "Packs.application" from application pack
libraryTarget: 'var',
}
})
// app/javascript/packs/application.js
export {
Greeter
}
<script>
$(document).ready(function(){
Packs.application.Greeter.hello();
});
</script>
The library name is arbitrary. Using the [name] placeholder is optional but allows you to export to separate modules if you're using multiple "packs".
As I cannot comment rossta's answer, here is what I had to do. My default config was:
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
module.exports = environment
and I just had to add the additionnal config in it:
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
environment.config.merge({
output: {
library: ['Packs', '[name]'], // exports to "Packs.application" from application pack
libraryTarget: 'var',
}
})
module.exports = environment
After that, as mentioned by rossta, each symbol which is exported in app/javascript/packs/application.js can be accessed from the DOM as Packs.application.<symbol>.
in app/javascript/packs/application.js:
import Greeter from '../greeter.js'
Greeter.hello()
and in app/javascript/greeter.js:
export default {
hello : function(){
console.log('hello')
}
}
I could fix the issue exposing Greeter object to window as follows:
export var Greeter = {
hello: function() {
console.log('hello');
}
}
window.Greeter = Greeter;
However, I am still looking for a Webpack way of accomplishing this.

How to convert this require statement to import using systemjs / es6?

so I am using angular2 / TypeScript and trying to convert this call which works fine today by the way:
window['Highmaps'] = require('highcharts/modules/map')(Highcharts);
to es6
something to the sorts of:
import * as Ng2Highcharts from 'highcharts/modules/map';
Ng2Highcharts(Highcharts)
but no luck as the former works but the es6 version does not.
this is the project by the way: https://github.com/Bigous/ng2-highcharts
and I have to convert it since I am trying to move from commonjs to systemjs,
thanks for any help,
Sean.
window['Highmaps'] = require('highcharts/modules/map')(Highcharts);
The value of the require is immediately applied as a function. This typically implies there's a default export that's being used.
Try
import Ng2Highcharts from 'highcharts/modules/map';
Ng2Highcharts(Highcharts)
To be able to import like that you need to create an entry in the system.config.js file or just System.config({...}), however you are doing the config.
One entry goes in the map and one in packages like this
// map tells the System loader where to look for things
var map = {
'app': 'app', // 'dist',
'rxjs': 'node_modules/rxjs',
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
'#angular': 'node_modules/#angular',
'highcharts': 'path/to/highcharts-directory'
};
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { defaultExtension: 'js' },
'highcharts/modules/map': { defaultExtension: 'js' }
};
And you're good to go.... Hope it works.

Resources