Rails/Webpack: Can't access a JS library - javascript

I'm trying to access the strftime JS library in my Rails application. I've followed the instructions on how to add it using yarn add and I've added a debugger statement in one of my react components so I can try and access it through the console. strftime is not defined and the instructions say this about requiring it:
var strftime = require('strftime') // not required in browsers
console.log(strftime('%B %d, %Y %H:%M:%S')) // => April 28, 2011 18:21:08
console.log(strftime('%F %T', new Date(1307472705067))) // => 2011-06-07 18:51:45
I believe require is a node.js thing because you can't "require" code in the browser. It states that its not needed in the browser but the instructions also say to add it as a <script> tag but if I have the webpacker gem, I should be able to just do something like:
import strftime from 'strftime'
in my application.js file right?
What is required to get access to a library from node_modules in my Rails app?
Edit:
Some context: This is an existing application running Rails 5.2.3 which is currently being migrated from sprockets/asset pipeline to React components with Webpack(er).
I've updated my environment.js file using the answer from #thanhnha1103.
If I open my app in Chrome for example and go to the console and type strftime it says strftime is not defined. However, if I add a debugger instruction in my application.js file just after import strftime from 'strftime' I get access to two objects called strftime__WEBPACK_IMPORTED_MODULE_0__ and strftime__WEBPACK_IMPORTED_MODULE_0___default.
Now if I do this in console while the JS has stopped because of debugger:
strftime = strftime__WEBPACK_IMPORTED_MODULE_0__
I have access to a function called strftime which is my intended purpose. (I also have to import strftime in my react component to get access to this but that's probably as intended. Any ideas about how to make this work out of the box without doing this in my application.js file?:
import strftime from 'strftime'
strftime = strftime__WEBPACK_IMPORTED_MODULE_0__
debugger // obviously I'll remove this later but this is for testing purposes.
console.log('Hello World from Webpacker')
// Support component names relative to this directory:
var componentRequireContext = require.context("components", true);
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.useContext(componentRequireContext);

You should try this in your config files:
// config/webpack/environment.js
const webpack = require('webpack');
const {environment} = require('#rails/webpacker');
environment.plugins.append(
'ProvidePlugin-Strftime', // arbitrary name
new webpack.ProvidePlugin({
strftime: 'strftime'
}),
);
module.exports = environment;

Related

Creating a DatePicker from CDN with React API

I am importing some React modules from CDN (that's not a requirement, I've also tried with a local build, more in the final question about it):
<script crossorigin src="https://unpkg.com/react-onclickoutside#6.9.0/dist/react-onclickoutside.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.0/moment.min.js" integrity="sha512-Izh34nqeeR7/nwthfeE0SI3c8uhFSnqxV0sI9TvTcXiFJkMd6fB644O64BRq2P/LA/+7eRvCw4GmLsXksyTHBg==" crossorigin="anonymous"></script>
<script crossorigin src="https://unpkg.com/react-datepicker#3.1.3/dist/react-datepicker.min.js"></script>
Then I have a script to build the React DatePicker component, this is the relevant snippet from it:
HelloWorld.Example=function()
{
var p,setCount,count,p$1,c,myDate,datePicker;
p=React$2.useState(0);
setCount=p[1];
count=p[0];
p$1=React$2.useState(new moment(new Date((c=Date.now(),DateUtil.DatePortion(c)))));
myDate=p$1[0];
datePicker=React$2.createElement(DatePicker.default,{
selected:new moment(new Date()),
onChange:p$1[1]
});
React.set_setCount(setCount);
return React$2.createElement("div",null,datePicker,React$2.createElement("p",null,(Html.textf(function($1)
{
The error that I see from the JS Console is:
react-datepicker.min.js:1 Uncaught TypeError: o is not a function
at Ee (react-datepicker.min.js:1)
when the script call ReactDOM.render.
Is there a way to understand what is o ? Maybe an import missing?
(Edit Well, looking at chrome debugger and comparing it to github, o is isValidDate, i.e. import isValidDate from "date-fns/isValid";, hence the imports from date-fns are not working from CDN )
Is there a way such that - for example - I can locally npm run build the needed module, react-datepicker, and then call the react API from my script as shown above? (a suggestion that I received was configuring my script as entry in webpack, but afaik React doesn't use webpack, though I see it is used in react-datepicker).
From React docs, I can read that
JSX is not a requirement for using React
so something like the above should be doable, in theory.
I've opened a question/issue on github react-datepicker repo (in the context of calling this component from WebSharper.React).
Is there a way such that - for example - I can locally npm run build the needed module, react-datepicker, and then call the react API from my script as shown above?
Yes, there is a well known solution!
Write an index.js as follows
import React from "react";
import DatePicker from 'react-datepicker'
import "react-datepicker/dist/react-datepicker.css";
export {ImportedComponent}
window.MyDatePicker = function MyDatePicker(props) {
console.log("props from window.MyDatePicker", props)
return React.createElement( DatePicker, props );
}
build via npm and copy the static folder from the build of your by npm run build to the SPA folder of your proj
copy
the 3 script tags from the index.html in the build into the index.html template of your proj
and
<div id="root"></div>
(of course you use a different id for your project app and
there will be nothing to render here)
in my case they are (they will be different for you)
<div id="root"></div>
<script>!function(e){function t(t){for(var n,l,p=t[0],f=t[1],i=t[2],c=0,s=[];c<p.length;c++)l=p[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(a&&a(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,p=1;p<r.length;p++){var f=r[p];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="/";var p=this.webpackJsonpcontent_npm=this.webpackJsonpcontent_npm||[],f=p.push.bind(p);p.push=t,p=p.slice();for(var i=0;i<p.length;i++)t(p[i]);var a=f;r()}([])</script>
<script src="/static/js/2.a6e4c224.chunk.js"></script>
<script src="/static/js/main.b075c560.chunk.js"></script>
Now go with
datePicker=React$1.createElement(window.MyDatePicker,{
selected:myDate,
onChange:p$1[1],
showTimeSelect: true,
});
in you SPA.js and enjoy any react component like this one from WebSharper.React!
Btw I had to pass a JS date, not a Moment date here in the selected of props, I'm not sure why, anyway, this is not relevant to the problem.
FYI, this is the F# code from WebSharper project
let myDate, setMyDate = WrapReact.UseState (DateTime.Today.JS)
let importDatePicker = JS.Eval("window.MyDatePicker") :?> React.Class
let propDP =
{
selected = myDate
onChange = setMyDate
showTimeSelect = true
}
let datePicker =
React.CreateElement( importDatePicker, propDP)
WrapReact.setCount <- setCount
div [] [
datePicker
p [] [Html.textf "You selected %s date %s time" (myDate.ToDateString()) (myDate.ToTimeString())]
Full open source project shared on github.
I think that the main problem is that WebSharper scripts are not JavaScript modules. In that case it should be immediate to import an external module or make the above SPA.js as the Webpack main entry. In fact it is well known that there are differences between <script type=module> and <script>
Module Script Execute in Strict Mode
Module Script has its Own Scope
Module Script can Import other Javascript Modules
Module Script has this as Undefined
Inline Module Script can have async Attribute
Module Script is Always Deferred
As confirmed by Adam Granicz indeed on WebSharper side:
that should be the way, yes, #Jand42 and others have been working on changing the current output to support modules and a better TS interoperability - this has been on the agenda for years, so closing it would be a good step forward
(In the meantime there are of course alternatives, e.g. flatpickr, which has bindings also for jQuery, instead of react-datepicker or pure React or F# Fable instead of WebSharper.React and so on)

How to import npm module in JavaScript stimulus

I want to use imageToZ64() from zpl-image module
I have installed it using: npm install zpl-image
and then I import it: import './../../../node_modules/zpl-image';
but when I use the fucntion like this let res = imageToZ64(canvas);
i'm getting : Uncaught (in promise) ReferenceError: imageToZ64 is not defined
I tried to import it like this: import { imageToZ64 } from './../../../node_modules/zpl-image/zpl-image';
but the problem is this function uses other functions from pako.js which is another js file in the zpl-image.
my question is how to import the module in a way that I can be able to access all the functions?
I highly recommend you read the README here : zpl-image repo GitHub
In order to use this with Node.js :
const imageToZ64 = require("zpl-image").imageToZ64;
Or :
const { imageToZ64, rgbaToZ64 } = require("zpl-image");
If you are trying to use it in the browser read generic browser usage
since you already installed it via npm there is a demo file in node_modules/zpl-image/zpl-image.html ,you can open it in the browser, read its content, and understand how the code works which is the purpose of the demo file.

Rails, webpacker: where/when to use the import statement in classes, and what's the purpose of requiring in the pack/application.js?

I'm upgrading an older application to Rails 6, which uses webpacker for all the JS asset management.
I'm using the pikaday calendar library, and have added it via yarn add pikaday, verified it shows up in packages.json and then added it to my app/javascript/packs/application.js via require("pikaday").
I have a JS class called Datepicker that I'm using to abstract the actual pikaday calendar. I'm doing this because I may someday change the datepicker implementation, and this way I'll only have to change a single class instead of update all my pikaday calls.
However, it doesn't seem to matter if I require("pikaday") in the application.js pack file or not; as long as I import Pikaday from "pikaday" in the class I'm referencing it in, it makes no difference.
Questions
I'm trying to gain understanding of what's going on.
Do I need to add require("pikaday") or import Pikaday from "pikaday" in the app/javascript/pack/application.js file? Why or why not?
I'm familiar with the principle that globals are bad and should be avoided, but is there a way to avoid having to import CLASS from "class_file" on every JS file that references it? In my example I want to use the Datepicker class in multiple places. The reason I ask is because I have 10+ classes like this, and it's a little annoying to have 10+ import statements at the top of every JS file that I want to use these in. There are certain classes that I always want access to. I've played with the webpacker ProvidePlugin functionality, but it complains that Datepicker is not a constructor, so I'm probably missing something but I don't have enough knowledge to know what.
app/javascript/custom/datepicker.js
import Pikaday from "pikaday"
export default class Datepicker {
constructor(element, options) {
if (options == null) { options = {} }
// Store DOM element for reference
this.element = element
// Do not re-run on elements that already have datepickers
if (this.element.datepicker === undefined) {
options = Object.assign({},
this.defaultOptions(),
options
)
const picker = new Pikaday(options)
// Store picker on element for reference
this.element.datepicker = picker
return picker
} else {
console.log("Datepicker already attached")
return
}
}
// Overridden by `options` in constructor
defaultOptions() {
return {
field: this.element,
format: "M/D/YYYY",
bound: true,
keyboardInput: false,
showDaysInNextAndPreviousMonths: true
}
}
}
app/javascript/packs/application.js
require("#rails/ujs").start()
require("turbolinks").start()
require("moment")
// Note: if using `#rails/ujs`, you do not need to use `jquery-ujs`.
import "jquery"
// Does not matter if I require this or not, as long as it is imported in the
// class file, I can remove this require statement and everything still works.
require("pikaday")
// StimulusJS
// Webpack's `require` looks for `controllers/index.js` by default
require("controllers")
require("custom/datepicker")
You asked a couple questions:
You only need import Pikaday from 'pikaday' in the file where you reference the imported variable, Pikaday. In this case, you only need this import in your custom datepicker module. You can remove require("pikaday") from your application.js pack file.
The reason for this is Webpack will take your application.js pack as an entry point to a dependency graph; starting there, it will recursively traverse each required/imported module, find the dependencies of those modules, and so on, until all declared modules are included in the bundle. Since you have declared import 'custom/datepicker' in the application.js pack, and the custom datepicker imports pikaday, it will get included in the bundle as dependency.
Your custom Datepicker gets compiled as an ES module (rather, Webpack's implementation of ES module), since you are using the ES module syntax export default .... This is important to the way ProvidePlugin works. From the Webpack 4 documentation of ProvidePlugin:
For importing the default export of an ES2015 module, you have to specify the default property of module.
This means your Webpack configuration for the plugin entry for Datepicker would look something like this (using the Rails Webpacker environment api):
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
const {resolve} = require('path');
environment.plugins.append('Provide', new webpack.ProvidePlugin({
Datepicker: [resolve('app/javascript/custom/datepicker'), 'default']
}))
module.exports = environment
Opinion: That said, I encourage you to make your imports explicit, e.g. import Datepicker from 'custom/datepicker' in each module where Datepicker is referenced. Even if repetitive, it will become much easier to integrate with tools like ESlint, which, with certain code editors, can provide inline feedback about errors from compilation—much easier to setup with explicit dependencies declared in each module.
I put together a working demo of Pikaday using your custom Datepicker with the ProvidePlugin here: https://github.com/rossta/rails6-webpacker-demo/commit/be3d20107c2b19baa8b9560bce05e0559f90086d

Production vs Test proxy es6 import

So I'm trying to import a different javascript file based on a boolean in my react application.
When I'm running it in dev mode I want to import my testProxy.js file which just returns back json data.
When i'm running the application in production I want to use prodProxy.js where it connects to my production application via ajax and returns back results, etc.
I have achieved this with webpack by doing:
new webpack.ProvidePlugin({
api: isDevBuild ? "./dev/api" : "./prod/api"
}),.
And then in my file I just declare the api.
declare var api: any (using typescript)
I am not really liking this and I don't know another way.
What I want is what's below
pseudo code below:
let _api = isDev ? import('devApi') : import('prodApi');
let someObject = new MyClass(_api);
Tried this and it fails.
let _api = isDev ? import('devApi') : import('prodApi');
let someObject = new MyClass(_api);
this is not possibe:
You must import all ES6 modules at the top level of your JavaScript files.
You can’t import an ES6 module based on a conditional.
you can do:
let _api = isDev ? require('./devApi') : require('./prod/api');
let someObject = new MyClass(_api);
I don't know what isDev is referring to. Asuming you have some node/browser setup, and isDev is based on a environment variable passed by process.env.DEVELOPMENT, this will not be available on the client.
You would need to use the webpack evironment plugin: https://webpack.js.org/plugins/environment-plugin/ and add the env variable to it, so u can access it from the client.
sidenote: added on build time, not on run time.
Hope this helps.

how to import seed module in javascript gnome-shell extension

It's quite simple: My js is part of a gnome-shell extension and contains several import lines:
[...]
const Gettext = imports.gettext;
const MessageTray = imports.ui.messageTray;
imports.searchPath.push("/opt/tempmon/lib"); // append custom search path
const Helper = imports.tempmon; // import helper module
[...implementation...]
I've compiled my shared lib with seed-module.h successfully and placed it under /opt/tempmon/lib/libseed_tempmon.so
Looking Glass (lg) displays:
gjs/seed (I'm confused) couldn't find the "JS-Module tempmon in search path".
What action is needed to get my module included in the JS environment?

Categories

Resources