Avoid relative path import hell in react-native? - javascript

I'm new to react-native coming from vue background, one thing I hate in react is having to use relative path imports in my components, I find myself doing something like:
import HomeScreen from '../../components/screens/HomeScreen'
If my folder structure changes it means I will have to do a search and replace in all of m components using that component, not cool and prone to be a dev hell.
Is it possible to use absolute paths like with node modules, is there a library where I can use aliases or similar, with vue I could import my components in the app.js like Vue.component('home-screen ...) and I could use them without having to import.

you can add a package.json file with a name key
{
"name": "#components"
}
in any folder and metro will recognize it.
You can then import as #components/screens/HomeScreen
If you are using vscode, you can add a jsconfig.json file to your project root to enable import autocomplete.
Here is mine:
{
"compilerOptions": {
"baseUrl": "",
"paths": {
"#components/*": ["src/components/*"],
"#helper/*": ["src/helper/*"],
"#actions/*": ["src/actions/*"],
"#constants/*": ["src/constants/*"],
"#primitives": ["src/primitives/index.js"],
"#graphql": ["src/graphql/index.js"],
"#services/*": ["src/services/*"],
"#assets/*": ["assets/*"]
}
}
}

The easy method
Add a package.json to important directories with a {"name":"<prefix>"}, similar to a monorepo.
If you are doing this, you can probably also think about making it a full monorepo since there is very little extra work
The incredibly complex method
This method is easier for using things like webpack resolver, etc.
Create a metro.config.js at the root with the following content
module.export = {
resolver: {
resolveRequest: (
context: ResolutionContext,
moduleName: string,
platform: string | null
) => {
//...
return Resolution;
}
}
}
Resolution Context
The resolution context is an object that holds attributes of the running resolution, most importantly originModulePath which is the path of the requiring module.
Resolution
The resolution is an object of either:
{ type: "empty" } for empty sources
{ type: "sourceFile", filePath: string } for JS, JSON, etc. files
{ type: "assetFiles", filePaths: string[] } for any other type (e.g. not metro compilable)
For this method, I would recommend looking at metro-resolver's types since this method is abysmally documented

Related

What is the equivalent of module.exports for ES6 Modules?

I have an existing package written in JavaScript that I'm trying to convert to TypeScript. My question is, what is the equivalent of module.exports for ES6 Modules?
For example if I have the following code:
module.exports = function () {
console.log("Hello World");
};
The only way I have found to do this in ES Modules is by doing something like the following.
const myFunc = function () {
console.log("Hello World");
};
export default myFunc;
// OR
export { myFunc };
Of course, this is not the same thing as module.exports = //....
Which leads to this problem. When I run this through TypeScript and get the outputted code, users of the package will either need to use an import defaultExport from "module-name"; statement (which isn't bad), or instead of being able to access it using require("module-name") they will have to use require("module-name").default (if they are using standard JS, not TypeScript). Which obviously is a breaking API change for standard JavaScript users.
Obviously, the goal here is to have no breaking API changes (and require no changes for customers code).
One more requirement to any solution. In other places in the package I need to be able to export a lot of different files as one object. So in the past I've been doing:
module.exports = {
"thing1": require("./a.js"),
"thing2": require("./b.js")
};
So I need to be able to export this as normal without requiring the user to do thing1.default. This one so far seems like the easier requirement since I'm exporting an object which ES Modules seems to handle really well. It's when you are exporting a singular (non-named) expression that it gets really tricky.
Is there a way to do this without having to make this a breaking change to the API?
The solution here is to use the TypeScript export = command. This allows you to use either require or import with the module.
export = function () {
console.log("Hello World");
};
import myModule from "./lib/myModule";
// OR
const myModule = require("./lib/myModule");
For more information the TypeScript documentation has a guide about Migrating from JavaScript, and specifically a section called Exporting from Modules.
Typescript recommends not to rewrite your code, but instead to configure it to allow js modules.
When we were working on converting a very large app from js to TS, we took the position of writing new code in TS, and slowly converting js into ts (but not spend a whole lot of time just converting).
We needed to enable few options in the tsconfig.json, such as :
{
...,
"compilerOptions" : {
...
"moduleResolution": "node",
"allowJs": true,
...
},
"include" [
"path/to/my/js",
]
}
you also need to update your webpack.config.js:
module.exports = {
// ... my settings
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
}
You might also have to declare require as a function (if you are getting errors such as require is undefined):
declare function require(path: string): any;
more info is available here :
https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html
Most accurate replacement for module.exports in typescript is export default
export default {
"thing1": require("./a.js"),
"thing2": require("./b.js")
}
then you can use them like below
import myThings from '.....'

Set a global service for import in Create React App

Is it possible to somehow declare a jsx file as a global file or a directory for import? For example instead of:
import { Navigation } from '../../../../../../helpers/NavigationService';
do just:
import { Navigation } from 'NavigationService';
I've seen it's possible in webpack config, but I don't see this file in create-react-app. Can I use somehow package.json for that?
There are three ways to solve your issue:
NODE_PATH (<- probably what you're looking for):
You can define an environment variable NODE_PATH=src/ and then you can import your service like so import { Navigation } from 'helpers/NavigationService';. It'll will work but this is not necessary what's best in my opinion.
No deep nesting:
No nesting, means no issue of deep nesting in the first place. You can try having a file hierarchy similar to this:
src/
/helpers
/components/
/componentA/
componentA
relatedComponent
otherRelatedComponent <- no need for nesting
/componentB/
...
The mono repo approach:
Having an internal helper package and have it imported like import { Navigation } from 'myproject-helpers/NavigationService'; may be a good compromise
You could set NODE_PATH='src' in .env file, using global imports instead, here's my solution, without having to eject.
.env:
NODE_PATH='src'
Create a folder src/services, inside it create the NavigationService, my example:
// src/services/NavigationService.js
export class NavigationService {
static runIt() {
console.log("Running");
}
}
In the App.js file, now you can import the navigation service directly, using global import, as follows:
// src/App.js
// ... import React and others
import { NavigationService } from "services/NavigationService";
class App extends Component {
componentDidMount() {
NavigationService.runIt();
}
// ... render method
}
If using VsCode, to get code completion, create jsconfig.json file, with the following:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"services/*": ["./src/services/*"]
}
}
}
I hope it helps!

FlowTypes how to import types from private npm module

I am writing private npm package to use internally, I also want to include some flowtypes in there that will be shared between internal projects and imported in following format import type {SomeType} from 'our-private-lib' But I am having trouble in doing so, what is the best approach to include flow types with npm package?
Currently I am transpiling all ES code with babel and then also using flow-copy-source to copy original files over along side transpiled files but with .js.flow extension, but then that means that the name of these files should be the same as the transpiled file?
For example if I have file in /super-schema/index.js with
export type SuperSchemaType = {
prop1: boolean,
prop2: string
}
const SuperSchema = {
prop1: true,
prop2: 'Hello'
}
module.exports = SuperSchema;
And package.json points to main file index.js which exports SuperSchema like so
module.exports = {
superSchema: require('./super-schema.js/index.js');
}
I then can import that like so
import {superSchema} from 'our-private-lib';
but what about flowtype? import type { SuperSchemaType } from 'our-private-lib'; Doesn't really work
Your general approach is correct (using flow-copy-source), but if you want to consume types out of the main entry point of your module, you need to export the types out one way or another. You can do that explicitly by doing something like
export type {
SuperSchemaType
} from './super-schema';
Or, if you're using babel 7, export type * might be useful for you
export type * from './super-schema';

Getting 'Module not found' although file exists

I downloaded the angular starter and ran the app and it ran well. Now I Added a new file "people.service.ts" located in "src/app/services/people.service.ts".
When I try to import it, I get an error:
Module not found: Error: Can't resolve 'services/people.service' in
'/path-to-project/angular-starter-master/src/app'
This is the code I use to import it (in src/app/app.module.ts):
import {PeopleService} from 'services/people.service';
I'm sure that there's no typos because the IDE recognizes it. There isn't any TypeScript error in the entire project. The file 'services/people.service' does contain a class named PeopleService.
Any help will be profoundly appreciated. Please let me know if you need any additional information.
The problem is that you're using absolute path instead of a relative path. Change the import to the following:
import {PeopleService} from './services/people.service';
TypeScript 2.0+
In TypeScript 2.0 you can add a baseUrl property in tsconfig.json:
{
"compilerOptions": {
"baseUrl": "."
// etc...
},
// etc...
}
Then you can import everything as if you were in the base directory:
import {PeopleService} from 'services/people.service';
On top of this, you could add a paths property, which allows you to match a pattern then map it out. For example:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"services/*": [
"services/validation/*"
]
}
// etc...
},
// etc...
}
Which would allow you to import it from anywhere like so:
import {PeopleService} from 'services/people.service';
From there, you will need to configure whatever module loader you are using to support these import names as well. Right now the TypeScript compiler doesn't seem to automatically map these out.
You can read more about this in the github issue. There is also a rootDirs property which is useful when using multiple projects.
OR
You can directly change the absolute path to relative path like this:
import {PeopleService} from './services/people.service';

How can I use a config file in React?

Let's say I have 5 jsx files and each file uses some config parameter.
My index.js file imports all of these 5 jsx files.
Instead of having my config data spread accross 5 files, is there a way for my jsx files to get the data from a global JS object which has loaded the data from a config file?
I've seen some examples, but I've not been able to get them to work.
JS6 import function | Example using webpack
Assuming ES6:
config.js
export const myConfig = { importantData: '', apiUrl: '', ... };
Then:
jsxFileOne.js, jsxFileTwo.js, ...
import { myConfig } from 'config.js';
There are other ways to import & export things globally leveraging webpack, but this should get you started.
If your project is built using Webpack, consider using node-env-file.
Example config file snippets:
development.env
API_SERVER_URL=https://www.your-server.com
webpack.config.js
const envFile = require('node-env-file');
...
const appSettingsFile = isDevBuild ? '/settings/development.env' : '/settings/production.env';
try {
envFile(path.join(__dirname + appSettingsFile));
} catch (error) {
console.log("Failed to read env file!: " + __dirname + appSettingsFile);
}
...
plugins: [
new webpack.DefinePlugin({
"process.env": {
API_SERVER_URL: JSON.stringify(process.env.API_SERVER_URL)
}
})
...
]
Inside your js/jsx code, you can now access process.env.API_SERVER_URL variable which will contain the required value.
It seems dotenv package is more popular, you can try this out as well.
Very old problem, that nobody took the time to solve, until now. I leave this for future readers because this is a top search result for configuration in React.
I created wj-config to deal exactly with this. Be sure to pay close attention to the React notes as you will need to enable top-level awaits in webpack, either by ejecting or using the #craco/craco NPM package.
You may also read this blog post that explains its use.

Categories

Resources