I've encountered something very peculiar in Javascript, which I can't explain. Simply put, I'm building a dynamic Button component in React in which one can optionally pass in the path of an icon file.
This approach works perfectly:
const image = require('../styling/assets/images/search-icon.svg');
<button className='button' onClick={props.handleButtonClick}>
<img className='button-icon' src={image} />
<p>{props.label}</p>
</button>
But it fails if I change the first line to these two:
const path = '../styling/assets/images/search-icon.svg';
const image = require(path);
In this second case I get this error: Error: Cannot find module '../styling/assets/images/search-icon.svg'
Fundamentally the two approaches seem equivalent to me but apparently not.
Why does the 2nd approach fail?
According to the internet: "require is resolved during packaging not during runtime".
https://github.com/facebook/react-native/issues/6391
According with MDN docs:
The import statement cannot be used in embedded scripts unless such
script has a type="module". Bindings imported are called live bindings
because they are updated by the module that exported the binding.
More information here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Related
I have a scenario in which I want to dynamically tell a child component which image to show, based on an required image passed down by the parent, as such:
Parent:
function Social() {
return (
<SocialBar>
{
SocialMedias.map((media) =>{
return <SocialApp
key={uuidv4()}
link={media.link}
social={require(media.icon)}
/>
})
}
</SocialBar>
);
}
Child
function SocialApp(props) {
return (
<Social href={props.link} target="_blank">
<CustomSizedImage src={props.social.default} />
</Social>
);
}
Which seems pretty straight forward, right? However, this causes the error below:
I've seen some posts say that this is caused because the path is not absolute. However, I tested with absolute paths and it still doesn't work. The funny thing is, when I changed the parent code to the below for testing, it worked.
New parent code:
function Social() {
const img = require("../assets/icons/social-media/instagram.svg");
return (
<SocialBar>
{
SocialMedias.map((media) =>{
return <SocialApp
key={uuidv4()}
link={media.link}
social={img}
/>
})
}
</SocialBar>
);
}
export default Social;
The result:
OBS: I tested with EVERY image that this code will possibly load. Is not an image/image path related issue.
What is expected of all this is to dynamically load icons for social media with the correct URLs, etc. What I do not understand is: why does it work when I require the image outside the return?. I know I could make this work by iterating media object outside of return and use the resulting array with the map index, but that doesn't seem clean. Why does it have this behavior? Is there a way to make it work inside the map?
Your last code snippet works because the path is known at compile time.
I assume you use a bundler like Webpack or similar. When they compile your project, they check all imports (including require calls), use that to calculate the dependency tree, and finally bundle all required files together. For images, that includes "compiling it into a module" which just returns the path to the image.
Usually that also involves replacing paths with module identifiers. E.g. if you ever looked at the built Webpack code, you'll notice that it doesn't use e.g. require('./module') but __webpack_require.c(5) or something similar. It gave the module './module' the unique ID 5 in the bundled code.
With your earlier code snippets, when using a dynamic require with a non-constant string, Webpack just doesn't know that you'll request that image. Nor would it know the new unique ID to use for that.
The easiest solution is to import your images somewhere and use those references dynamically:
// images.ts
export IconA from './media/icon-a.svg';
export IconB from './media/icon-b.svg';
// somewhere else
import { IconA, IconB } from './images';
const iconPath = someCondition ? IconA : IconB;
Of course, you don't have to use the import/require mechanic. You can simply have your icon path be an actual path, relative to your public/ folder.
I'm kind of going nuts here.
I need to start writing a stand alone non web application in JavaScript.
My problem is that I can run (using nodejs) the script just fine, but I can't split the code that I need to write among multiple files because then I don't know how to include them.
Rigth now all I want is the simplest working example of including a JS file inside another so I can use the functions defined there (or class if I so choose).
My test.js looks like this.
const { outputText } = require("./text_module.node.js");
outputText();
While my text_module.node.js looks like this:
function outputText(){
console.log("hello world");
}
My package.json looks like this:
{
"type": "text_module.node.js"
}
I have tried
Adding export before the function. It tells me export is unexpected
Using this notation: import { something } from "a_path". Anything that uses this notation will get me an "Unexpected token {" error.
The current configuration simply tells me that outputText() is not a function. So I'm out of ideas.
I don't know how else to search. I've been searching for hours on this topic and it seem that no matter where I look there is some HTML code that is needed to tie everything togeter or using another third party tool. Using jQuery loads stuff "asynchronically" which is not what I need. I want it to sequential.
Is JS just NOT suppossed to be used like other scripting languages? Otherwise I can't figure out why this is so complicated. I feel like I'm missing something big here.
If you want to use const { outputText } = require("./text_module.node.js");,
export it as module.exports = { outputText: outputText }.
In your question,
Adding export before the function. It tells me export is unexpected
Because it's an es6 thing and nodejs doesn't support all es6 features. You can refer to this answer for more details on that.
I have been playing around with the General Transit Feed Specification - Realtime, and I am following exactly the example given on google's documentation:
https://developers.google.com/transit/gtfs-realtime/examples/nodejs-sample
for JavaScript, using my cities local transit feed, however I keep encountering the following error:
var feed = GtfsRealtimeBindings.FeedMessage.decode(body);
^
TypeError: Cannot read property 'decode' of undefined
I have a working implementation in python that follows their example for that, so I can verify with certainty that the feed is the correct format.
I am running the code using the instruction:
node index.js
My package.json includes all the relevant dependencies, and I have installed using npm the required packages.
I have searched far and wide for a solution to this error but have had no luck. Any advice?
Looking at the current example code on GitHub
(https://github.com/MobilityData/gtfs-realtime-bindings/tree/master/nodejs#example-code)
it seems you're missing transit_realtime in between:
GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(body);
On the example there is a link to the github of the Javascript language binding. The github example differs from the documentation example. I figure the documentation is simply out of date.
the line in the google documentation example
var feed = GtfsRealtimeBindings.FeedMessage.decode(body);
should be var feed = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(body); note the inclusion of .transit_realtime.
Alternatively this could be expressed in the line:
var GtfsRealtimeBindings = require('gtfs-realtime-bindings');
as var GtfsRealtimeBindings = require('gtfs-realtime-bindings').transit_realtime;
and the google documentation example code beyond that line would remain as it is.
You need to import gtfs-realtime-bindings by referencing the transit_realtime property. It's a change from the old protobuf.js bindings generator, but this is done to separate namespaces.
It won't work
cont GtfsRealtimeBindings = require('gtfs-realtime-bindings');
It will:
const GtfsRealtimeBindings = require('gtfs-realtime-bindings').transit_realtime;
Now you can get feedMessage and decode it.
const { FeedMessage, FeedHeader, FeedEntity, VehiclePosition } = GtfsRealtimeBindings;
I read a bit about answers but haven't found something that's what I'm looking for. Please refer me to a relevant question and delete my question if it's after all a duplicate.
I've got the following file structures:
components
icons
bitcoin_icon.png
ethereum_icon.png
...
CoinsList.js
CoinsTable.js
As seen in the code, I have in CoinsTable an array of objects called supportedCoins, whose elements are CryptoCoin objects, and those have the property iconURL which stores the path of images (which are icons).
As seen in the code, in Coinstable.js I have code that renders a table that displays all the elements of supportedCoins, each property separately, using their object properties (e.g supportedCoins['bitcoin'].symbol), but when trying to render the following for a CryptoCoin, for example, again with supportedCoins['bitcoin']:
<img src={supportedCoins['bitcoin'].iconURL, the rendered src is the actual path, but React doesn't render the picture.
That's what I want to do and what I want for to work.
What I found out that does work is doing the following to do in CoinsTable.js:
Import the icon from the right path, for example:
import biticonURL from './coins_icons/bitcoin_icon.png - same path as the one in supportedCoins['bitcoin'].iconURL`
Create an image as desired, only with src={biticonURL}:
`
To compare the different things I tried, here are the resulted srcs of supportedCoins['bitcoin']:
- With supportedCoins['bitcoin'].iconURL: ./coins_icons///bitcoin_icon.png
- With bitcoinIcon(import): /static/media/bitcoin_icon.b6168699.png
Why am I getting wrong paths, and why does store my pictures there? The images aren't stored on my server in those paths.
Why am I getting wrong paths with triple forward slashes?
You're getting the ./coins_icons///bitcoin_icon.png path because of how you concatenate strings in CoinsList.js. Here's the relevant code:
const coinsIconsDir = './coins_icons/';
supportedCoins['bitcoin'] = new CryptoCoin ('BTC', 'Bitcoin', coinsIconsDir + '/' + '/bitcoin_icon.png');
Notice that there is one forward slash at the end of coinsIconsDir, one as a single character, and one at the beginning of '/bitcoin_icon.png', which is why you're getting that triple slash. The simplest way to fix that is to just pick one consistent place for the forward slash, like the following for example:
const coinsIconsDir = './coins_icons';
supportedCoins['bitcoin'] = new CryptoCoin ('BTC', 'Bitcoin', `${coinsIconsDir}/bitcoin_icon.png`);
Now your path will be ./coins_icons/bitcoin_icon.png as desired.
Why are my pictures stored under the static/media/ path?
You're using a build and bundle system to run your app. That system is taking the resources you are importing and storing them under the static/media/ folder before running your web app. Based on the static/media/ path, I'm guessing that you're using create-react-app, which will take images and other static assets and place them in a static folder in the final build. That's described here in the documentation. The most relevant lines are
Webpack finds all relative module references in CSS (they start with ./) and replaces them with the final paths from the compiled bundle.
This ensures that when the project is built, Webpack will correctly move the images into the build folder, and provide us with correct paths.
So what does that mean for your case?
I'm going to give you a number of alternatives for importing images and you can pick the one you like best.
If you fix the forward slashes, you should be able to use:
<img src={supportedCoins['bitcoin'].iconURL} style={{maxHeight:'25px'}}/>
However, if ./coins_icons/bitcoin_icon.png is not publicly available on your server (which is probably the case if you're using create-react-app), this will not work.
In that case, you'll either need to put the image somewhere that's publicly available (in create-react-app that's the public folder), or continue to import them as you are doing. There are several different ways to import, including using the syntax you currently have:
import bitcoinIcon from './coins_icons/bitcoin_icon.png';
If you're using Webpack, you can also use require. Note that require needs at least some file path information, so you'll need to do the file path construction inside the require.
// Assuming supportedCoins['bitcoin'].iconName has been initialized to the icon's file name
// e.g., supportedCoins['bitcoin'].iconName === bitcoin_icon.png
<img src={require(`./coins_icons/${supportedCoins['bitcoin'].iconName}`)} style={{maxHeight:'25px'}}/>
However, requires outside of the top level
and dynamic requires can be considered bad practice. To avoid that you could change CoinsList.js to:
import bitcoinIcon from './coins_icons/bitcoin_icon.png';
import ethereumIcon from './coins_icons/ethereum_icon.png';
import dashIcon from './coins_icons/dash_icon.png';
import iotaIcon from './coins_icons/iota_icon.png';
var supportedCoins = new Array();
const coinsIconsDir = './coins_icons/';
supportedCoins['bitcoin'] = new CryptoCoin ('BTC', 'Bitcoin', bitcoinIcon);
supportedCoins['ethereum'] = new CryptoCoin('ETH', 'Ethereum', ethereumIcon);
supportedCoins['dash'] = new CryptoCoin('DASH', 'Dash', dashIcon);
supportedCoins['iota'] = new CryptoCoin('IOTA', 'IOTA', iotaIcon);
and then in CoinsTable.js do:
<img src={supportedCoins['bitcoin'].iconURL} style={{maxHeight:'25px'}}/>
I'd pick this last option because it keeps the coin definition contained inside one file and keeps all imports static.
I have a SPA (in Aurelia / TypeScript but that should not matter) which uses SystemJS. Let's say it runs at http://spa:5000/app.
It sometimes loads JavaScript modules like waterservice/external.js on demand from an external URL like http://otherhost:5002/fetchmodule?moduleId=waterservice.external.js. I use SystemJS.import(url) to do this and it works fine.
But when this external module wants to import another module with a simple import { OtherClass } from './other-class'; this (comprehensiblely) does not work. When loaded by the SPA it looks at http://spa:5000/app/other-class.js. In this case I have to intercept the path/location to redirect it to http://otherhost:5002/fetchmodule?moduleId=other-class.js.
Note: The Typescript compilation for waterservice/external.ts works find because the typescript compiler can find ./other-class.ts easily. Obviously I cannot use an absolute URL for the import.
How can I intercept the module loading inside a module I am importing with SystemJS?
One approach I already tested is to add a mapping in the SystemJS configuration. If I import it like import { OtherClass } from 'other-class'; and add a mapping like "other-class": "http://otherhost:5002/fetchmodule?moduleId=other-class" it works. But if this approach is good, how can I add mapping dynamically at runtime?
Other approaches like a generic load url interception are welcome too.
Update
I tried to intercept SystemJS as suggest by artem like this
var systemLoader = SystemJS;
var defaultNormalize = systemLoader.normalize;
systemLoader.normalize = function(name, parentName) {
console.error("Intercepting", name, parentName);
return defaultNormalize(name, parentName);
}
This would normally not change anything but produce some console output to see what is going on. Unfortunately this seems to do change something as I get an error Uncaught (in promise) TypeError: this.has is not a function inside system.js.
Then I tried to add mappings with SystemJS.config({map: ...});. Surprisingly this function works incremental, so when I call it, it does not loose the already provided mappings. So I can do:
System.config({map: {
"other-class": `http://otherhost:5002/fetchModule?moduleId=other-class.js`
}});
This does not work with relative paths (those which start with . or ..) but if I put the shared ones in the root this works out.
I would still prefer to intercept the loading to be able to handle more scenarios but at the moment I have no idea which has function is missing in the above approach.
how can I add mapping dynamically at runtime?
AFAIK SystemJS can be configured at any time just by calling
SystemJS.config({ map: { additional-mappings-here ... }});
If it does not work for you, you can override loader.normalize and add your own mapping from module ids to URLs there. Something along these lines:
// assuming you have one global SystemJS instance
var loader = SystemJS;
var defaultNormalize = loader.normalize;
loader.normalize = function(name, parentName) {
if (parentName == 'your-external-module' && name == 'your-external-submodule') {
return Promise.resolve('your-submodule-url');
} else {
return defaultNormalize.call(loader, name, parentName);
}
}
I have no idea if this will work with typescript or not. Also, you will have to figure out what names exactly are passed to loader.normalize in your case.
Also, if you use systemjs builder to bundle your code, you will need to add that override to the loader used by builder (and that's whole another story).