How to use symlinks in React Native projet? - react-native

The symlink support is still not officially available in react-native https://github.com/facebook/metro/issues/1.
It's actually possible to use symlinks in the package.json with npm (not yarn)
{
"name": "PROJECT",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"my_module1": "file:../shared/my_module1/",
"my_module2": "file:../shared/my_module2/",
"react": "16.8.3",
"react-native": "0.59.5",
},
"devDependencies": {
"babel-jest": "24.7.1",
"jest": "24.7.1",
"metro-react-native-babel-preset": "0.53.1",
"react-test-renderer": "16.8.3"
},
"jest": {
"preset": "react-native"
}
}
Although we will get my_module1 does not exist in the Haste module map
To fix this we could do before a metro.config.js (formerly rn-cli.config.js)
const path = require("path")
const extraNodeModules = {
/* to give access to react-native-firebase for a shared module for example */
"react-native-firebase": path.resolve(__dirname, "node_modules/react-native-firebase"),
}
const watchFolders = [
path.resolve(__dirname, "node_modules/my_module1"),
path.resolve(__dirname, "node_modules/my_module2"),
]
module.exports = {
resolver: {
extraNodeModules
},
watchFolders,
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false
}
})
}
}
Unfortunately it doesn't work anymore on react-native 0.59 The app is reloading, but changes in the source code are not reflected in the app. Anyone has a clue to achieve this?

I had a similar issue and found haul.
Follow haul getting started instructions.
Add your local dependencies with file:../ e.g:
// package.json
"dependencies": {
"name-of-your-local-dependency": "file:../"
}
reinstall node_modules with yarn --force
Start the development server by yarn start (haul will replace your start script)
Run react-native run-android or/and react-native run-ios

None of the answers I found worked for me and it seems symlinks are not going to be supported anytime soon (see: https://github.com/facebook/metro/issues/1), so I had to do it manually.
I am using onchange npm package and running that in my local package: onchange 'dist/**/*.js' -- cp -R ./dist ../../app/node_modules/#author/packagename
That worked for me to achieve development and testing, while not breaking anything for production releases. I hope this can save my peers a few headaches.

After a few years working on this, we found a reliable way using yarn.
Add your local dependencies with are inside a folder file:../CompanyPackages/package e.g:
// package.json
"dependencies": {
"local-package": "file:../CompanyPackages/local-package"
}
Use a custom metro.config.js
const path = require("path")
const { mapValues } = require("lodash")
// Add there all the Company packages useful to this app
const CompanyPackagesRelative = {
"CompanyPackages": "../CompanyPackages",
}
const CompanyPackages = mapValues(CompanyPackagesRelative, (relativePath) =>
path.resolve(relativePath)
)
function createMetroConfiguration(projectPath) {
projectPath = path.resolve(projectPath)
const watchFolders = [...Object.values(CompanyPackages)]
const extraNodeModules = {
...CompanyPackages,
}
// Should fix error "Unable to resolve module #babel/runtime/helpers/interopRequireDefault"
// See https://github.com/facebook/metro/issues/7#issuecomment-508129053
// See https://dushyant37.medium.com/how-to-import-files-from-outside-of-root-directory-with-react-native-metro-bundler-18207a348427
const extraNodeModulesProxy = new Proxy(extraNodeModules, {
get: (target, name) => {
if (target[name]) {
return target[name]
} else {
return path.join(projectPath, `node_modules/${name}`)
}
},
})
return {
projectRoot: projectPath,
watchFolders,
resolver: {
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
extraNodeModules: extraNodeModulesProxy,
},
}
}
module.exports = createMetroConfiguration(__dirname)

You can use yalc. Yalc will simulate a package publish without actually publishing it.
Install it globally:
npm i -g yalc
In your local package:
yalc publish
In the application:
yalc add package-name && yarn
After you made some changes to the package, you can just run
yalc push
and it will automatically update every app that uses your local package

Could never get my own environment working using any other suggestions, but found a hack that works well (though not ideal) that can be easily set up in just a few lines of code and without changing your RN project configuration.
Use fs.watch for changes recursively in the directory where you're working on your library, and copy the updates over whenever there's been a change:
import fs from 'fs'
const srcDir = `./your-library-directory`
const destDir = `../your-destination-directory`
fs.watch("./src/", {recursive: true}, () => {
console.log('copying...')
fs.cp(srcDir, destDir, { overwrite: true, recursive: true }, function() {
console.log('copied')
})
})

Only two things are required.
1.yarn --force
2.yarn start
then select android for running option

Related

Error: While trying to resolve module #apollo/client React Native

after installing new version of apollo client getting this Error. I tried other versions and to downgrade but nothing. Also I tried to specify in metro.config.js to resolve "cjs" type of file (#apollo/client/main.cjs), but nothing.
Error
error: Error: While trying to resolve module `#apollo/client` from file `****\src\api\queries\home.js`, the package `****\node_modules\#apollo\client\package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`****\node_modules\#apollo\client\main.cjs`. Indeed, none of these files exist:
Dependencies
"#apollo/client": "^3.3.2",
"graphql": "^15.4.0",
Anyone can help me please? Will be very thankful!
As documented at https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md#apollo-client-354-2021-11-19, the solution should be to add
const { getDefaultConfig } = require("metro-config");
const { resolver: defaultResolver } = getDefaultConfig.getDefaultValues();
exports.resolver = {
...defaultResolver,
sourceExts: [
...defaultResolver.sourceExts,
"cjs",
],
};
in your metro.config.js.
In my case, I already have a module.exports generated by default, so I just had to make the file so:
const {getDefaultConfig} = require('metro-config');
const {resolver: defaultResolver} = getDefaultConfig.getDefaultValues();
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
...defaultResolver,
sourceExts: [...defaultResolver.sourceExts, 'cjs'],
},
};
Simply adding cjs file extension to metro.config.js works for me.
According to expo's Official Adding more file extensions to assetExts documentation...
const { getDefaultConfig } = require('#expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push('cjs');
module.exports = defaultConfig;
I have exactly the same problem in react-native.
From the documentation, it follows that you need to add the ability to handle "cjs" files.
https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md#apollo-client-354-2021-11-19
Solved the problem today by adding to node_modules/metro-config/src/defaults/defaults.js
export.sourceExts = ["js", "json", "ts", "tsx", "cjs"];
and from the project folder
for android:
cd android && ./gradlew clean
for ios in xcode :
clean -> run
For Expo Projects, We need to add cjs to sourceExts.
const { getDefaultConfig } = require('#expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.sourceExts.push('cjs');
module.exports = defaultConfig;
Apollo Docs for source extension https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md#apollo-client-354-2021-11-19
Expo syntax for metro config https://docs.expo.dev/guides/customizing-metro/

Removing log from production mode is not working - react native

React native documentation state that
"You can also use this babel plugin that removes all the console.* calls. You need to install it first with npm i babel-plugin-transform-remove-console --save-dev, and then edit the .babelrc file under your project directory like this"
{
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}
I didn't find .babelrc file. Hence I made the following changes in babel.config.js file.
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
env: {
production: {
plugins: ["transform-remove-console"]
}
}
};
But it's not working for me. The logs are displayed when I've tested them in Android Studio. What went wrong here?
The documented approach didn't work for my existing project either, but it worked in a new test project. I tried numerous dependency upgrades, adding/removing deps, plugins, etc. trying to figure out the source of the problem, but no luck. So finally I went with this workaround:
module.exports = {
plugins: ['#babel/plugin-proposal-numeric-separator', 'lodash'].concat(
process.env.NODE_ENV === 'production' ? ['transform-remove-console'] : []
),
presets: [
'module:metro-react-native-babel-preset',
['#babel/env', { targets: { node: 6 } }]
]
};
This should work (recommended method)
const presets = ['module:metro-react-native-babel-preset'];
const plugins = ['module:react-native-dotenv'];
if (process.env.ENV === 'prod') {
plugins.push('transform-remove-console');
}
module.exports = {presets, plugins};
Or if you don't want to use any package for this you can do this.
Place this in index.js
if(!__DEV__){
console.log = () => {}
}

Cannot use Vue component library with npm link

I am building a Vue application and a Vue component library at the same time. So, I would like to setup the library with npm link so I don't have to keep publishing my library package and re-install it in my main application.
My package will be called #company/vue. I can publish it to npm and install/use it in my Vue app like this:
import Vue from 'vue';
import VueComponents from '#company/vue';
Vue.use(VueComponents);
And that works fine. I can access the components in my .vue files and all that.
However, if I follow the standard steps to linking my library:
cd path/to/library
npm link
cd path/to/application
npm link #company/vue
Upon starting dev mode, I get this warning:
export 'default' (imported as 'VueComponents') was not found in '#company/vue'
And of course, nothing in the page loads.
I have to imagine that I'm possibly bundling it wrong?
My build script looks like this:
vue-cli-service build --no-clean --target lib --name company-vue src/index.js
And my index.js that it refers to:
import './index.scss';
import * as components from './components/index.js';
const CompanyVue = {
install(Vue) {
if (this.installed) {
return;
}
this.installed = true;
for (let name in components) {
Vue.use(components[name]);
}
}
};
let GlobalVue = null;
if (typeof window !== 'undefined') {
GlobalVue = window.Vue;
}
else if (typeof global !== 'undefined') {
GlobalVue = global.Vue;
}
if (GlobalVue) {
GlobalVue.use(Plugin);
}
export * from './components';
export default CompanyVue;
This is just the standard way I've seen most libraries do it. And again, it works fine if I pull the package from npm.
This is the stuff related to my bundling in my package.json:
"files": [
"dist",
"src",
"!src/**/*.spec.js",
"!src/**/*.stories.js"
],
"main": "./dist/company-vue.common.min.js",
"module": "./dist/company-vue.umd.min.js",
"browser": "./dist/company-vue.umd.min.js",
"unpkg": "./dist/company-vue.umd.min.js"
And finally, my babel config for the component library:
module.exports = {
presets: ['#babel/preset-env'],
env: {
test: {
presets: [[
'#babel/preset-env',
{
targets: { node: "current" }
}
]]
}
}
}
i found a solution in this issue:
just add this to your vue.config.js in your project:
configureWebpack: {
resolve: {
symlinks:false //npm link
},
}

How to keep my keys in safe in react native (expo)?

I have 3 release channels - dev, qa, prod.
const ENV_MODES = {
dev: {
},
prod: {
},
qa: {
}
}
ENV_MODES.default = ENV_MODES.prod
const getEnvVars = channel => {
if (process.env.NODE_ENV !== 'production') return ENV_MODES.dev
if (channel === null || channel === undefined || channel === '') return ENV_MODES.prod
if (channel.startsWith(ENV_DEV)) return ENV_MODES.dev
if (channel.startsWith(ENV_QA)) return ENV_MODES.qa
if (channel.startsWith(ENV_PROD)) return ENV_MODES.prod
return ENV_MODES.default
}
const ENV = getEnvVars(Constants.manifest.releaseChannel)
But I don't want to put keys into the repo.
How should I handle this? As I understand I can't expect that I will have NODE_ENV === 'qa' when I will publish in QA channel
You could use react-native-dotenv and add your keys to a .env file and add it to .gitignore. This way you won't be pushing keys to your repo and you can change your variables depending on the environment your code is running on.
To use the lib you only need to add it to your devDependencies and add it to you babel.config.js file, like so:
module.exports = function (api) {
api.cache(true);
return {
presets: [
'babel-preset-expo',
'module:react-native-dotenv',
],
};
};
EDIT:
NODE_ENV won't be the same as your release channel. If you want to load configs based on the release channel use Expo.Constants.manifest.releaseChannel.
However have in mind this variable doesn't exist in Dev mode, as per expo's docs.
Expo.Constants.manifest.releaseChannel does NOT exist in dev mode. It does exist, however when you explicitly publish / build with it.
EDIT 2:
Here's an example on how you can achieve both individual configurations for each release channel and use react-native-dotenv to avoid pushing secrets to your Git repo (since this is a big no no).
Remember: add your .env file to your .gitignore.
Constants.js
// eslint-disable-next-line import/no-extraneous-dependencies
import { AWS_KEY } from 'react-native-dotenv';
import { Constants as ExpoConstants } from 'expo';
const getChannelConfigs = (releaseChannel = '') => {
switch (releaseChannel) {
case 'qa':
return {
API_URL: 'https://qa.com/api',
...
};
case 'prod':
return {
API_URL: 'https://prod.com/api/',
...
};
default:
return {
API_URL: 'https://dev.com/api/',
...
};
}
};
const CHANNEL_CONFIGS = Object.freeze(getChannelConfigs(ExpoConstants.manifest.releaseChannel));
export default { AWS_KEY, CHANNEL_CONFIGS };
.env
AWS_KEY='superSecretKeyDoNOTStealThx'
In this example we are configuring which API URL the app will call based on its release channel. We're also avoiding commiting keys to our Git repo, since we now have them in a cozy .env file.
It's also worth mentioning this setup works when building a standalone app in your CI, but handing secret keys to your users might not be the best idea.
Expo now has eas.json file that sits in root of your app. That file can define all the build profiles and variables if needed. The submit feature of the configuration helps manage all the app store related configs.
Sensitive secrets (prod ids and paths) , feature flags are all possible to configure.
https://docs.expo.dev/build-reference/variables/
{
"build": {
"production": {
"node": "16.13.0",
"env": {
"API_URL": "https://company.com/api"
}
},
"preview": {
"extends": "production",
"distribution": "internal",
"env": {
"API_URL": "https://staging.company.com/api"
}
}
}
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json",
"track": "internal"
},
"ios": {
"appleId": "john#turtle.com",
"ascAppId": "1234567890",
"appleTeamId": "AB12XYZ34S"
}
}
}
}

How to use gulp-load-plugins with Browser-Sync?

I'm at a point in my Gulp usage that it's starting to make sense splitting tasks up into separate files.
For this, I was hoping to use gulp-load-plugins, but although my task runs, Browser-Sync doesn't appear to fire / do anything.
Here's my stripped back setup. Not sure where I'm going wrong!
gulpfile.js
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')();
function getTask(task) {
return require('./gulp/tasks/' + task)(gulp, plugins);
}
gulp.task('browser-sync', getTask('browser-sync'));
gulp.task('bs-reload', getTask('bs-reload'));
gulp.task('scripts', getTask('scripts'));
gulp.task('default', ['browser-sync'], function(){
gulp.watch('src/js/**/*.js', ['scripts']);
});
scripts.js (Gulp Task)
var browserSync = require('browser-sync').create();
module.exports = function(gulp, plugins) {
return function() {
return gulp.src([
'src/js/plugins/**',
'src/js/app.js',
'!src/js/{libraries,libraries/**}'
])
.pipe(plugins.plumber({
errorHandler: function(error) {
console.log(error.message);
this.emit('end');
}}))
.pipe(plugins.concat('app.js'))
.pipe(gulp.dest('dist/js/'))
.pipe(plugins.rename({
suffix: '.min'
}))
.pipe(plugins.uglify({
preserveComments: 'none'
//preserveComments: 'some'
}))
.pipe(gulp.dest('dist/js/')) // Seems okay up until here...
.pipe(browserSync.reload({ // ...but this never seems to fire!
stream: true
}));
};
};
You'll notice I'm requiring Browser-Sync in my scripts.js file, but this is because doing plugins.Browser-Sync wasn't working. I read somewhere that this is because Browser-Sync is not actually a Gulp plugin.
So whilst I don't get any errors and Browser-Sync appears to start up...from then on, my scripts task works, but it doesn't fire the BrowserSync part.
Any help greatly appreciated!
PS, Incase it helps, here's my package.json file (notice that above I'm presenting you a stripped back version of my gulpfile.js).
package.json
{
"name": "MichaelPumo",
"version": "1.0.0",
"description": "A frontend Gulp project for WordPress by Michael Pumo.",
"main": "gulpfile.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "MichaelPumo",
"license": "MIT",
"devDependencies": {
"browser-sync": "2.6.5",
"gulp-postcss": "~6.0.0",
"autoprefixer-core": "~5.2.1",
"gulp-sourcemaps": "~1.5.2",
"gulp-concat": "2.5.2",
"gulp-plumber": "1.0.0",
"gulp-rename": "1.2.2",
"gulp-sass": "2.0.4",
"gulp-uglify": "1.2.0",
"gulp": "~3.9.0",
"node-neat": "~1.7.2",
"gulp-svgmin": "~1.2.0",
"gulp-image-optimization": "~0.1.3",
"gulp-load-plugins": "~1.0.0"
}
}
For anyone wondering or in the dark about this, note that by default gulp-load-plugins will only work with NPM packages that are prefixed with 'gulp-'.
Fortunately, you can change this behaviour and pass the search as an option like so:
gulpfile.js
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')({
pattern: '*'
});
In your tasks, you can now reference Browser-Sync as plugins.browserSync like so:
browser-sync.js (Gulp Task)
'use strict';
module.exports = function(gulp, plugins) {
return function() {
plugins.browserSync.init({
port: 8080,
proxy: {
target: 'http://localhost:8888/my-website-path/'
}
});
};
};
Hope this helps someone else!