I would need to get the filename of an imported class:
fileA.js
export default class User {
}
fileB.js
import User from './fileA'
function getClassFilename(constructor) {
// do something like __filename, but to get the filename where User is defined rather than the current filename
}
console.log(getClassFilename(User.constructor)) // fileA.js
This is the general idea. However the actual use case is based on decorators:
fileA.js
import someDecorator from './decorator'
#someDecorator
class User {
}
decorator.js
export default function (target) {
// can I somehow get the target filename without passing it as a property?
}
That information isn't available to you by default, the module in question would have to provide a means of accessing the information.
You've mentioned __filename so I'm assuming you're using Node. The module providing User could provide that information like this:
export const SourceFilename = __filename;
Note taht there's no in-spec way to do that without Node's __filename (but there's one under consideration and reasonably far down the path toward being added).
Updated answer for updated question: There's nothing stored on the class (constructor) User that provides this information. So again, the code defining User would need to provide that information (as a property on User, as something you can get from the module and pass separately to the decorator, etc.). Otherwise, it simply isn't available to you.
Related
I'm working on a custom i18n module and would love to replace this code (this is a an "about-us" page):
const messages = (await import(`./about-us.${locale}.json`))
.default as Messages;
By
const messages = (
await import(`./${__filename.replace('.tsx', `.${locale}.json`)}`)
).default as Messages;
Unfortunately __filename resolves to /index.js (I guess because of Webpack?) - is there any way to achieve what am I trying to do in my example or this would need to be built-in Next.js directly to work?
Refactor this so consumers don't know about the filesystem
Spoiler: I'm not going to tell you how to access __filename with Next.js; I don't know anything about that.
Here's a pattern that is better than what you propose, and which evades the problem entirely.
First, setup: it sounds like you've got a folder filled with these JSON files. I imagine this:
l10n/
about-us.en-US.json
about-us.fr-FR.json
contact-us.en-US.json
contact-us.fr-FR.json
... <package>.<locale>.json
That file organization is nice, but it's a mistake to make every would-be l10n consumer know about it.
What if you change the naming scheme later? Are you going to hand-edit every file that imports localized text? Why would you treat Future-You so poorly?
If a particular locale file doesn't exist, would you prefer the app crash, or just fall back to some other language?1
It's better to create a function that takes packageName and localeCode as arguments, and returns the desired content. That function then becomes the only part of the app that has to know about filenames, fallback logic, etc.
// l10n/index.js
export default function getLang( packageName, localeCode ) {
let contentPath = `${packageName}.${localeCode}.json`
// TODO: fallback logic
return JSON.parse(FS.readFileSync(contentPath, 'utf8'))
}
It is a complex job to locate and read the desired data while also ensuring that no request ever gets an empty payload and that each text key resolves to a value. Dynamic import + sane filesystem is a good start (:applause:), but that combination is not nearly robust-enough on its own.
At a previous job, we built an entire microservice just to do this one thing. (We also built a separate service for obtaining translations, and a few private npm packages to allow webapps to request and use language packs from our CMS.) You don't have to take it that far, but it hopefully illustrates that the problem space is not tiny.
1 Fallback logic: e.g. en-UK & en-US are usually interchangeable; some clusters of Romance languages might be acceptable in an emergency (Spanish/Portuguese/Brazilian come to mind); also Germanic languages, etc. What works and doesn't depends on the content and context, but no version of fallback will fit into a dynamic import.
You can access __filename in getStaticProps and getServerSideProps if that helps?
I pass __filename to a function that needs it (which has a local API fetch in it), before returning the results to the render.
export async function getStaticProps(context) {
return {
props: {
html: await getData({ page: __filename })
}, // will be passed to the page component as props
};
}
After a long search, the solution was to write a useMessages hook that would be "injected" with the correct strings using a custom Babel plugin.
A Webpack loader didn't seem like the right option as the loader only has access to the content of the file it loads. By using Babel, we have a lot more options to inject code into the final compiled version.
I have a class called Tour that has a method with a dependency on another class called City
I can put the dependency at the top of the file, as in
const City = require './City'
class Tour {
...
but it seems a bit odd to have the dependency listed before the class like that.
I can move it to the method where I use it, as in:
insertAfter(afterCityName, newCityName, appendAtEnd=true) {
const City = require('./City');
const cities = this.cities;
...
but having it within a method also seems not ideal.
Ideally I would think the constructor but every attempt I've made has been the wrong syntax, for examples:
constructor(cities=[]) {
this.cities = cities;
...
const City = require('./City'); // No, assigned but never used
City = require('./City'); // No, City is not defined
this.City = require('./City'); // No, City is not defined
}
How to place it in the constructor (and still Capitalize The Class) ?
The convention I've seen and prefer is at the very top of the file. This makes it abundantly clear what your imports are. "Sneaky imports" in the middle of the file can lead to surprises.
When you're refactoring things and need to move files around it's easy to click through one file to the next quickly inspecting the top to check that the import paths are set correctly. This is not the case with inline imports.
Keep in mind require() is being retired, it's import from here on out, where import works best at the top of the file, as in:
import City from './City';
This style fits in well with other conventions like Python, Ruby, or C and C++ which use conventions like #include <city.h>.
I am trying to set a global variable (NOT A GLOBAL ENV VARIABLE) in Cypress.io. I would like to abstract a url that I will use over and over again AND something that I can use in multiple files (not just one).
Also, I do not want to set it as baseurl. I already have that set and I want to leave that alone.
Can anyone help me with this?
this can be done more easily than that :
in your cypress.json add
"env": {
"YourVarName":"YourVarValue"
}
And in your code you can access to your var with :
const myGolabalVar = Cypress.env("YourVarName")
That's all you need
I recently found the answer in another blog on github linked here: https://github.com/cypress-io/cypress/issues/1121
But the answer in this blog was answered my Brian Mann with...
"TL;DR - just use modules, not globals.
Cypress is just JavaScript. All of the underlying principles about structuring files apply to Cypress as it would your own application files.
In this case, you keep mentioning variables. Variables are things defined in a particular file and are never global. This means it's not possible for them to be shared. Variables are accessible based on the local scope in which they are defined.
In order to make them global you have to attach them to a global object: window. However, there's no reason to do this, Cypress automatically has module support built in. This enables you to import functions into each spec file, thus making them way more organized and obvious than using globals.
We have recipes of this here: https://docs.cypress.io/examples/examples/recipes.html#Node-Modules"
I hope this helps someone else who is on Stackoverflow for this answer!
You can use the cypress.json file and set enviornment values there. If you do not want to use .env values, then you create any constants file you like and import it in to via the cypress index.js file.
Create a constants file,
e.g., cypress/constants.js
with a json object inside:
export const constants = {
VALUEA: 'my text for value A',
VALUEB: 'example value b'
}
in support/index.js
add a reference to your constants.js file:
import '../constants';
This will automatically import the constants dictionary into all of your spec files.
All you have to do to reference your constants in your spec files, is to use "constants.VALUEA"
for example:
cy.get('#button-label-id).should('contain.text', constants.VALUEB);
You can create a variable and a task in plugins/index.ts
// global variable
let counter = 0;
// you can then write a task to fetch this value
// For more info, visit https://on.cypress.io/plugins-api
module.exports = (on, config) => {
require("#cypress/code-coverage/task")(on, config);
// custom tasks
on("task", {
getCounter() {
counter++;
console.log("new counter value: ", counter);
return counter;
}
};
// reset this value before every test run
on('before:run', (details) => {
counter = 0;
return details;
});
});
Use this in your test cases as
cy.task("getCounter").then((counter) => {
console.log("counter", counter);
});
I'm getting rather confused as to if something is possible or not.
I create a module that contains the following:
export function logText(){
console.log('some text');
}
export class Example {
constructor(){
logText();
}
}
The intent is for the user to call new Example to start off the module logic.
import { logText, Example } from 'example';
// Do some magic here to modify the functionality of logText
new Example();
Is it possible for the end user to modify logText?
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
I frequently see repos with lots of functions exported that are useless without the users having to remake almost all the functionality manually, making it rather pointless to do. One good example is this repo whre theuy even call the exported functions their 'API'. In that example these are rather pointless exports and at worse would just cause issues if someone tried to use them in conjunction with the main function. But if you could modify them and have them still run then it would make sense to me.
Given this:
import { logText, Example } from 'example';
Is it possible for the end user to modify logText?
Since you aren't being very specific about what you mean by "modify logText", I'll go through several options:
Can you reassign some other function to the variable logText?
No. You cannot do that. When you use import, it creates a variable that is const and cannot be assigned to. Even if it wasn't const, it's just a local symbol that wouldn't affect the other module's use of its logText() anyway. The import mechanism is designed this way on purpose. A loader of your module is not supposed to be able to replace internal implementation pieces of the module that weren't specifically designed to be replaced.
Can you modify the code inside of the logText function from outside of the module that contains it?
No, you cannot. The code within modules lives inside it's own function scope which gives it privacy. You cannot modify code within that module from outside the module.
Can you replace the logText() function inside the module such that the implementation of Example inside that class will use your logText() function?
No, you cannot do that from outside the module. You would have to actually modify the module's code itself or someone would have to design the Example interface to have a replaceable or modifiable logText() function that the Example object used.
For example, logText() could be made a method on Example and then you could override it with your own implementation which would cause Example's implementation to use your override.
Code in the module that you do not modify:
export class Example {
constructor(){
this.logText();
}
logText() {
console.log('some text');
}
}
Code doing the import:
import { Example } from 'example';
class MyExample extends Example {
constructor() {
super();
}
logText() {
console.log("my own text");
}
}
let o = new MyExample();
Can you create your own version of logText and use it locally?
Sure, you can do that.
function myLogText() {
do your own thing
}
And, you could even NOT import logText so that you could use the symbol name logText() locally if you wanted. But, that won't affect what Example does at all.
Are there ways to design the example module so that that logText() can be easily replaced.
Yes, there are lots of ways to do that. I showed one above that makes logText a method that can be overriden. It could also be passed as an optional argument to the Example constructor.
There could even be an exported object that allowed the caller to replace properties on that object. For example:
export const api = {
logText: function logText(){
console.log('some text');
}
};
export class Example {
constructor(){
api.logText();
}
}
Then, use it like this:
import { api, Example } from 'example';
api.logText = function() {
console.log('my Text');
};
I would generally not recommend this because it sets you up for usage conflicts between multiple users of the same module where each one tries to modify it globally in ways that conflict with each other. The subclassing model (mentioned above) lets each use of the module customize in its own way without conflicting with other usages of the module.
Is it possible for the end user to modify logText?
No, that's not possible, import bindings are immutable, and function objects are basically immutable wrt the code they contain.
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
Why not make the log function an optional argument in the constructor? Usually when something is variable it becomes a parameter.
export class Example {
constructor(log=logText){
log();
}
}
I have a js file in my Angular application, data.js . This js file has some variables declared in it, something like below.
var data = 'test'
Now I have to access these variables and their values in my component (app.component.ts).
I read some where that declaring them as exports make them into modules and those can be accessed anywhere, But I'm not sure how this can be done.
This is the structure of my application. I have data.js in assets->js folder.I need to modify the variable value in app.component.ts.
I'm very new to Angular. Is this even possible?
With the file in your assets, I am guessing you are declaring it on the window. You will need the include the script in your index.html, and then access it on the window within your component via window.data. This is not really the recommended way of doing this unless your use case dictates it. The module approach you mentioned is preferred.
Next to your app.component.ts, create a file called data.ts, with:
export let data: string = 'data';
In your app.component.ts, import it using:
import { data } from './data.ts';
If you plan to not mutate that data, consider using the const keyword instead (in data.ts).
Directory structure
/app.component.ts
/data.ts
/...
Edit: Show Global Approach
You will need to include your script outside of the context of the Angular application. If you bootstrapped your application using the Angular CLI, you can add a reference to it in the cli configuration file. See this documentation on the topic.
That file will be included and will be available for access within your component on the window. The tricky part comes with typing and the Window. And example may look like this.
class AppComponent extends Component {
private data: string;
constructor() {
// Explicitly cast window as an any type. Would be better to type this, but this should work for you.
this.data = (<any>window).data;
}
}
(referrring to https://stackoverflow.com/a/42682160)
first you have to include the script into your src/index.html like
< script src="/assets/js/data.js">< /script>
important is that the above statement is placed before your angular root component tags
(< root-component>< /root-component> or < ion-app>< /ion-app> or something like that)
then you can simply write (for example inside app.component.ts ngOnInit function)
let varFromJsFile = window["data"] // varFromJsFile = 'test'
You want the variable to be a member of a Component class, not just a variable declared anywhere within a module.
If this doesn't make sense right away, you need to look more carefully at some basic Angular code samples.
Also, as long as you're using Angular and therefore TypeScript, it's better the declare variables using let or const.