I am writing my first codemod using Jscodeshift.
My current goal is to export a const that is assigned a certain identifier.
So that, if I target every variable named stuff, it will be named-exported after the script runs.
IN:
const stuff = 4;
OUT:
export const stuff = 4;
This is a stripped down version of what I have. It sort of works but it looks very brittle and has a number of drawbacks.
const constName = "stuff";
module.exports = (fileInfo, api) => {
const j = api.jscodeshift;
const root = j(fileInfo.source);
const declaration = root.find(j.VariableDeclaration, {
declarations: [
{
id: {
type: "Identifier",
name: constName
}
}
]
});
declaration.forEach(n => {
n.insertBefore("export");
});
return root.toSource();
};
AST
This will result in (notice the unwanted new line)
export
const stuff = 4;
This also crucially fails if this source is fed to the script.
IN:
// hey
const stuff = 4;
OUT:
export
// hey
const stuff = 4;
I am quite convinced that n.insertBefore("export"); is really the culprit here, and I'd like to build the named export myself using jscodeshift builders but really can't get it work.
Any suggestions here?
.insertBefore is not the right method to use. This is for inserting a whole new node before another node.
How want to replace a VariableDeclaration with an ExportNamedDeclaration. If you look at the AST for export const stuff = 4; you can see that it has a property declaration whose value is a VariableDeclaration node. That makes the transformation easy for us: Find the VariableDeclaration, create a new ExportNamedDeclaration, set it's declaration property to the found node and replace the found node with the new node.
To find out how to build the node we can look at ast-type's ast definitions.
const constName = "stuff";
module.exports = (fileInfo, api) => {
const j = api.jscodeshift;
return j(fileInfo.source)
.find(j.VariableDeclaration, {
declarations: [
{
id: {
type: "Identifier",
name: constName
}
}
]
})
.replaceWith(p => j.exportDeclaration(false, p.node))
.toSource();
};
astexplorer
Related
I would like to import images dynamically from the public folder, but this isn't working- anyone know why?
const modules = import.meta.glob("/Images/*.jpg");
const imagePaths = [];
for (const path in modules) {
modules[path]().then(() => {
const p = new URL(path, import.meta.url);
const data = {
path: p.pathname,
};
imagePaths.push(data);
});
}
First you need to adjust the path that you pass to the import.meta.glob function. Specify a path relative to the file that this code is in, since "/Images/*.jpg" will refer to the root folder on your system (because it starts with a slash).
Next you can use {eager: true} option to resolve the promise at compile time (you import just the URL, no need to use code splitting).
const imagePaths = [];
// note relative path vvv vvv this gets rid of promises
Object.values(import.meta.glob("./assets/*.jpg", { eager: true })).forEach(
({ default: path }) => {
const url = new URL(path, import.meta.url);
const data = {
path: url.pathname,
};
imagePaths.push(data);
}
);
/* imagePaths will have content like this:
[ {"path":"/src/assets/logo.jpg"}, {"path":"/src/assets/logo.jpg"} ]
*/
You can also take a look at the documentation here: https://vitejs.dev/guide/features.html#glob-import
I have a js object in which I return my endpoint addresses from api. This is a very nice solution for me, it looks like this:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
},
};
Now I can call this address in my redux-saga to execute the xhr query:
import { api } from 'utils';
const requestURL = api.users.notifications;
But I'm a bit stuck because now I have a problem - base path is missing here: '/users'.
Now when I call api.users, then I get a object. I would like to have a default value after calling the object like:
import { api } from 'utils';
const requestURL = api.users; // http://localhost:3000/Users
const requestURL2 = api.users.notifications; // http://localhost:3000/Users/notifications
I know that I could add a new string with the name 'base' to the object and add '/Users' there, but I don't like this solution and I think, there is a better solution.
You could do one of the following:
extend the String class
const API_BASE_URL = "http://localhost:3000"
const USERS = "/Users"
class UsersEndpoints extends String {
constructor(base) {
super(base)
}
// this is still a proposal at stage 3 to declare instance variables like this
// if u want a truly es6 way you can move them to the constructor
checkEmail = (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
notifications = `${API_BASE_URL}${USERS}/notifications`
messages = `${API_BASE_URL}${USERS}/messages`
}
// you can use userEndpoints itself as a string everywhere a string is expected
const userEndpoints = new UsersEndpoints(API_BASE_URL)
export default {
users: userEndpoints
}
The previous is just actually equivalent to
...
const userEndpoints = new String(API_BASE_URL)
userEndpoints.notifications = `${API_BASE_URL}${USERS}/notifications`
...
Obviously this is not recommended: you should not extend native classes, there are many disadvantages to this approach.
An obvious example is that there could be a conflict between the properties you use and the properties that might be brought by the native class
override the toString method
...
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
toString: () => API_BASE_URL
},
};
// this is actually not much different than the previous method, since a String is an objet with an overridden toString method.
// That said this method is also not recommended since toString is used in many places in native code, and overriding it just to substitute a string value will make information get lost in such places, error stacks for example
Achieve what u want using the language features intended for such a use case
What you are asking is to make the same variable to have different values in the same time, which is not possible in the language syntax, and it makes sense because it makes it hard to reason about code.
that being said i recommend something of the following nature
// it is also better to use named exports
export const getUsersEndpoint = ({
path = "",
dynamicEndpointPayload = {},
} = {}) => {
switch (path) {
case "notifications":
return `${API_BASE_URL}${USERS}/notifications`
case "messages":
return `${API_BASE_URL}${USERS}/messages`
case "checkEmail":
return `${API_BASE_URL}${USERS}/${dynamicEndpointPayload.email}/checkEmail`
// you still can do checkEmail like this, but the previous is more consistent
// case "checkEmail":
// return (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
default:
return `${API_BASE_URL}`
}
}
// you can use it like this
getUsersEndpoint() // returns the base
getUsersEndpoint({path: 'notifications'})
You can extend prototype to achieve this behaviour:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
const users = `${API_BASE_URL}${USERS}`
const baseUrls = {
checkEmail: (email) => `${users}/${email}/checkEmail`,
notifications: `${users}/notifications`,
messages: `${users}/messages`,
}
Object.setPrototypeOf(users.__proto__, baseUrls);
export default {
users
};
Try having object will all user endpoint and a function that return a value of a end point
const user = {
default: '/users',
notification: '/notification',
profile: '/profile',
getEndPoint(prop) {
if(this[prop] === 'default' ){
return this[prop];
} else {
if(this[prop]) {
return this.default + this[prop];
}
}
}
}
So you can have more end points that come under user and you can simply call
const requestURL = api.user.getEndPoint('default'); // http://localhost:3000/Users
const requestURL2 = api.user.getEndPoint('notifications'); // http://localhost:3000/Users/notification
I have multiple utility methods like
export const makeTextUtils = ({ U }) =>
Object.freeze({
hello: () => "Hello World!",
lorem: () => "Lorem ipsum..."
)};
these child utils can reference each other
export const makeOutputUtils = ({ U }) =>
Object.freeze({
logHello: () => console.log(U.txt.hello())
)};
Now I want to expose the Utils in a utils.js and inject the parent Method into all children
import { makeTextUtils } from './text';
import { makeOutputUtils } from './output';
// dependency injections
const textUtils = makeTextUtils({ U });
const outputUtils = makeTextUtils({ U });
// building parent util method
export const U = Object.freeze({
txt: textUtils,
out: outputUtils
});
I've tried various ways of importing the U at the top of the main file and switching up the order within the file, but nothing seems to do the trick.
Any help would be much appreciated.
First declare the U object at the top so that you can pass it down to the other functions. Then, after U gets properties assigned to it, you can freeze it:
import { makeTextUtils } from "./text";
import { makeOutputUtils } from "./output";
export const U = {};
// dependency injections
const textUtils = makeTextUtils({ U });
const outputUtils = makeOutputUtils({ U });
// building parent util method
Object.assign(U, {
txt: textUtils,
out: outputUtils
});
Object.freeze(U);
U.out.logHello();
https://codesandbox.io/s/happy-benz-cu3n5
The passing of U inside an object and immediately destructuring it in the utility functions doesn't seem to do anything - unless there's a particular reason for that, feel free to just pass the U object alone (and to use the U parameter alone).
How to make following use case with Flow and nodejs?
Requirements:
have Type definitions in a separate file(s), for now lets say all in Types.js
to use it inside another file/module:
const MyRequiredType = require('Types').MyRequiredType;
const methodWithInputTypeCheck = function(request: MyRequiredType){ }
Tried to use flow-aliases, but this seems to work only when have the declaration in the same file.
Thanks.
Oh, it seems that at the end it is far more simpler.
Looks like we can use std. classes:
1./ Types.js
// #flow
const Types =
{
SomethingWithUserRequest: class MyClass {
userId: string; //thanks to Babel
userNick: string;
}
};
module.exports = Types;
2./ SomeModule.js:
const SomethingWithUserRequest = require('../service/Dto/Types').SomethingWithUserRequest;
const TestFacade =
{
testFlow: function(
request: SomethingWithUserRequest
) {
console.log('userId', request.userId);
}
};
module.exports = TestFacade;
Only the IDE is still confused and does not hint in this way.
What is the difference between
module.exports = UpgradeService;
and
module.exports = { UpgradeService };
When I used the second one, I wasn't able to peek its definition in VS code. Why this is happening and what are the similarities and differences between them?
The first statement sets the exported value to UpgradeService. The second statement sets the exported value to an object. The { UpgradeService } is a shorthand for { UpgradeService: UpgradeService } which is a simple key:value pair! In other words, it exports a plain object that has only one (own) key: UpgradeService.
Remember that setting module.exports = (something) really is just changing what you get when you require() the module, and that (something) can be any value. You could set module.exports = 42 and require() would return the number 42 just fine.
Doing module.exports = { UpgradeService } means that you're setting the export to an object, which looks like {"UpgradeService": UpgradeService}. That follows the ES6 syntax rule where {x, y} is the same as {x: x, y: y}.
Then in your other files, instead of doing const UpgradeService = require('blah'), you do const UpradeService = require('blah').UpradeService, or const { UpgradeService } = require('blah') with destructuring.
Usually you set module.exports to an object (instead of a function or class) when you plan on exporting multiple things. For example, you might want to export both UpgradeService and, later, a new Upgrade class; in that case, you would do module.exports = { UpgradeService, Upgrade }.
Some people prefer to always start with exporting an object like that, because then it's easy to add a new exported thing. For instance, if you change module.exports = 'Apple' to module.exports = { fruit: 'Apple', animal: 'Bat' }, you have to change all files which required that module. But if you had just started with module.exports = { fruit: 'Apple' }, you would be able to add animal: 'Bat' without having to change any existing files.