NextJS: Force dependency with outputStandalone option - reactjs

With the new outputStandalone experimental feature (https://nextjs.org/docs/advanced-features/output-file-tracing#automatically-copying-traced-files-experimental) we can, after the build, have a standalone folder that contains the necessary dependencies, we simply copy it into our docker and don't need to rebuild inside the docker. It automatically detects the necessary dependencies in the source code UNLESS that dependency is only used in our package.json.
We use cross-env to start our Next app and of course this library is never imported in our source code, yet it needs to be present in the node_modules of the standalone folder.
So how can I force #vercel/nft to include a specific dependency ?

My case was that our dockerized Next.js project used Knex.js, where migrations were invoked with a npm script. Following this, some dependencies only required for knex.migrations() were never included in the standalone output.
Credits to sbstn, you can use unstable_includeFiles to create a list of dependencies, which should be included in the standalone output. This solution scans for .js and .json in a node_modules/ directory.
It should be highlighted that this setting is not available inside next.config.js, but as a page config prop, meaning it has to be exported inside a export const config = {} from a page different from _app.jsx and /api routes.
// pages/index.jsx
export default function Home(){
...
}
const requiredStandaloneDependencies = [
// some required deps that have not been included in standalone
"commander",
"debug",
"escalade",
"get-package-type",
"getopts",
"interpret",
"lodash",
"pg-connection-string",
"rechoir",
"resolve-from",
"tarn",
"tildify",
];
export const config = {
unstable_includeFiles: requiredStandaloneDependencies.map(
(dep) => `node_modules/${dep}/**/*.+(js|json)`
),
};
As a final thought for my case, where migrations are invoked with a custom npm script, npm run migrate:latest, I should also be able to circumvent the issue using programmatically invoked migrations. I'm just unsure as to how I add the method to the Next.js project tree to be run during runtime initialization.

Related

requestAnimationFrame is not defined it Next.js with React Native Web (Animated module)

I'm working on Next.js and React-Native-Web. I managed to run them together following the official Next.js example but when I'm trying to use the Animated package from the react-native it fails with Error that the requestAnimationFrame isn't defined. Basically this functionality does the node_modules package but I set the alias in webpack to translate all react-native requires to the react-native-web so even the node_modules package should use the react-native-web.
Any suggestions on how to solve it?
ReferenceError: requestAnimationFrame is not defined
at start (...node_modules\react-native-web\
dist\cjs\vendor\react-native\Animated\animations\TimingAnimation.js:104:11)
enter code here
Thanks for any help!
The problem is in the missed RequestAnimationFrame functionality at the server. This error happens when Next.js tries to render the component during SSR.
Unfortunately, there is no polyfill, etc. for such purpose so I just decided to use the Next.js dynamic imports for a Component that has animation functionality.
Next.js Official documentation
My own case оust to show how code looks:
import dynamic from 'next/dynamic';
const AutocompleteDropdown = dynamic(
() => import(
'myAwesomeLib/components/dropdown/autocomplete/AutocompleteDropdown'
),
{
ssr: false,
}
);
Now you can use the AutocompleteDropdown as the standard JSX component
I'm coding an App with React Native Web and NextJS 12, and in 2021 I encounter this problem and I fixed it, but now I know my fix was only for Next Dev, because it returned for Next Production Build.
Solution details:
No Dynamic import (which is useful too, but can be annoying when having lot of components using it)
Using RAF polyfill and Webpack ProvidePlugin.
Main thing to have in mind is that next.config.js with webpack 5 is going to check the codes first before even reach next entry points _documents.js and _app.js. It means that, you can put polyfill in those entry point files, it will still raise error of RAF undefined. You have to make requestAnimationFrame ready for config check.
DEV approach that will work on Next DEV only. Install RAF package https://www.npmjs.com/package/raf and In next.config.js add codes:
const raf = require('raf');
raf.polyfill();
This will add requestAnimationFrame and cancelAnimationFrame function to global and window object if they don't have it. In our case, it would add it in global for NodeJS.
But this solution won't work when executing npm run dev. I don't know why, if anyone knows why Next or Webpack 5 act differently from DEV to PRODUCTION, let me know.
Complete Solution:
Use ProvidePlugin config of webpack 5 https://webpack.js.org/plugins/provide-plugin/ . Create a file to use as modules, let's say: raf.js in root project or anywhere you want:
const raf = require('raf');
const polys = {};
raf.polyfill(polys);
module.exports = polys.requestAnimationFrame;
And in next.config.js use it inside webpack: () = {} like:
webpack: (config, options) => {
// console.log('fallback', config.resolve.fallback);
if (options.isServer) {
// provide plugin
config.plugins.push(
new options.webpack.ProvidePlugin({
requestAnimationFrame: path.resolve(__dirname, './raf.js'),
}),
);
}
And now, it's up to you to adapt to your existing config logic. By doing this, in Production Build, NextJS is injecting the requestAnimationFrame function in Server Side everywhere a module is using it.

Specify Next.js build directory in build command?

Originally, I had my distDir set to a folder within my Firebase Functions directory for deployment, however, the duplication of React when running the app in dev mode led to some errors, so I've had to comment it out.
Is there a way to specify the next build commands output directory within the command? Like next build dir='../functions/next'.
Or is there a way to separate dev builds directory (from the next command) from the production builds (next build) within the config?
I found following solution for this problem:
You can set custom environment variable that decides where your build will be generated:
BUILD_DIR=myBuilds/buildXYZ yarn build
OR
BUILD_DIR=myBuilds/buildXYZ npm build
In the next.config.js you can use your environment variable like this:
module.exports = {
distDir: process.ENV.BUILD_DIR || '.next',
}
Now your build will be dynamically generated in the myBuilds/buildXYZ directory defined in the BUILD_DIR variable.
One thing to notice: If you want to use the next export command (or any other command using the build directory), you should also add the environment variable before it, e.g.:
BUILD_DIR=myBuilds/buildXYZ yarn export
According to https://github.com/zeit/next.js/issues/1513, this may need you to update to the next version. But it looks like they added a feature to next.config.js file. All you have to do is add the destination for your dist:
module.exports = {
options: {
dist: '.next'
}
}
Haven't tested it, but it was successfully merged to master and marked as resolved.

React Component env variables from project for imported component

I have a Create React Project that has some local env variables for config.
I have a component that uses the env variables to configure itself. It imports the variables using
process.env.MY_VAR;
I wanted to move the component out into a npm package but retain the ability to use the process.env.MY_VAR. When I build and publish the package it will build it with the current MY_VAR and not the MY_VAR that is in my project I want to import it into. Is there a way I can do what I am after.
I have considered refactoring the component to take the env vars as props, but that's not something I really want to do.
Is there a way I can do what I am after.
How about treating your component library as a npm package that already is built fully/independent of build variables and ready to be consumed by npm clients?
Unless you want to use the library as some sort of internal package that naturally has a lot of coupling to the surrounding build environment, it is probably easier to embrace the classic npm approach. And it makes sense especially, if you want to publish the package. In your importing app project, you could still use process.env.MY_VAR. For the library part , I would suggest to hand in a configuration object.
If process.env.MY_VAR is important for the whole library containing multiple components, start with a package initialization step:
import { init } from "my-comp-library"
const config = {
myVar: process.env.MY_VAR
}
const lib = init(config)
const MyComp = lib.myComp()
const App = () => <MyComp />
If only relevant to a single component inside library, you could pass it as props directly:
const App = () => <MyComp myVar={process.env.MY_VAR} />
In app you could e.g. create a wrapper component MyAppComp that already includes its configuration in order to avoid redundant initialization logic for MyComp. Additional benefit here is that you would just have one place in app to change code, if library public API changes.
MyAppComp.js in app project:
import { MyComp } from "my-comp-library"
export const MyAppComp = props => <MyComp myVar={process.env.MY_VAR} {...props} />
Independent build management of your app and component project removes coupling, which makes them easier to maintain.
If you created project via create-react-app then you can use env-cmd npm package. If you have webpack then you can use dotenv package as well. In alternative way you may use below code to define the variable.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),

Webpack not including exported components

I have a React application, and I'm building the React components inside a seperate library so that it can be shared throughout other applications. As the React Components are private, they're installed in NPM via git+ssh, rahter than as an NPM package.
The basics:
My component Library has this structure:
-- components
---- component1.jsx
---- component2.jsx
---- index.js
-- package.json
The library's package.json file looks basically like this:
{
"name": "my-react-components",
"version": "1.0.0",
"description": "Collection of React components",
"main": "components/index.js",
"dependencies": {
...
}
}
The components/index.js file looks like this
export * from './component1';
export * from './component2';
Each component is written like this:
export const SomeComponent = () => { ... };
My application references these components like this
import { SomeComponent } from 'my-react-components';
My NPM dependency looks like this
"my-react-components": "git+ssh://{gitaddress}"
Here's my applications webpack config
Since I'm developing this library in unison with my application, I have been using a symlink from the library folder to the application's node_modules folder to allow the references. This has been working, and is continuuing to work fine.
The problem
When I don't use a symlink and instead use the version installed by NPM, babel or webpack or some part of the puzzle suddenly stops building my component library, and instead of referencing the components, I end up with the export * from ... tags in my compiled bundle. Cleaning the package and returning to the symlink method, and everything works fine again.
Webpack shows no errors, everything appears to build fine. I can see the difference in output however, in the symlink method, I can see each file being written into the bundle:
[1] ./node_modules/my-react-components/components/component1.jsx
[2] ./node_modules/my-react-components/components/component1.jsx
// Something like that I just made that up,
// the point is I see the paths for components that are being referenced.
When I don't use the symlink I instead just see something like
[1] ./node_modules/my-react-components/components/index.js
and as previously mentioned, that file is basically dropped straight in, no babel, or checking imports or anything.
So why does this happen with an NPM installed module, but not the exact same files in the exact same location through a symlink?
Just to note they are definitely the exact same files, I've done this immediately after pushing my lib and making no changes, deleting the node_modules folder and reinstalling, re-symlinking etc. The difference in the two methods is reproducable every time.

Requiring unknown module "crypto" in react-native environment

I'm writing a simple Twitter app using react-native. Using twit module to get twitter feeds and stream. Below is the code, it works fine node. However, when included into my react-native app, seeing error "Requiring unknown module "crypto"". Dependency seems to be myapp->twit->oauth->crypto (thats part of node v0.12.2). Any suggestions to get this working inside react-native environment?
var Twit = require('twit')
var T = new Twit({
consumer_key:''
, consumer_secret:''
, access_token:''
, access_token_secret:''
})
var filtered_tweets=[];
var error;
var isSuccess=false;
function getTweets(searchString){
T.get('search/tweets',{q:searchString, count:100}, getResponse);
}
function getResponse(err,data,response){
if(err) {
handleGetErr(err);
}
handleGetData(data.statuses);
}
function handleGetErr(err){
enter code here
error = err;
}
function handleGetData(data){
data.map(function(tweet){
var twit={
twit:tweet.id,
created_at:tweet.created_at,
text:tweet.text,
retweet_count:tweet.retweet_count,
favorite_count:tweet.favorite_count
};
filtered_tweets.push(twit);
});
console.log(filtered_tweets);
isSuccess=true;
}
getTweets("#sahaswaranamam");
module.exports = getTweets;
![attached][2]
The crypto module is a built-in Node module; React Native runs JS on JavaScriptCore (when on the device or simulator) and on Chrome itself (when using Chrome debugging), so modules that depend on built-in Node.js modules won't work. See the JavaScript Runtime section of the React Native docs for more info.
I'm not sure how hard it would be to integrate into a React Native app, but browser module bundlers like Browserify often have browser versions of core Node.js modules, like this one for crypto.
If you are using rn-nodeify as #emmby suggests, then you can use react-native-crypto. Instructions from the README:
Install
npm i --save react-native-crypto
# install peer deps
npm i --save react-native-randombytes
react-native link react-native-randombytes
# install latest rn-nodeify
npm i --save-dev mvayngrib/rn-nodeify
# install node core shims and recursively hack package.json files
# in ./node_modules to add/update the "browser"/"react-native"
# field with relevant mappings
./node_modules/.bin/rn-nodeify --hack --install
rn-nodeify will create a shim.js in the project root directory
// index.ios.js or index.android.js
// make sure you use `import` and not require!
import './shim.js'
// ...the rest of your code
But rn-nodeify also states:
If you're looking for a saner approach, check out ReactNativify. I haven't tested it myself, but I think philikon will be happy to help
With ReactNativify you create a rn-cli.config.js and then in a transformer.js you let Babel transform bundle dependencies using babel-plugin-rewrite-require:
// The following plugin will rewrite imports. Reimplementations of node
// libraries such as `assert`, `buffer`, etc. will be picked up
// automatically by the React Native packager. All other built-in node
// libraries get rewritten to their browserify counterpart.
[require('babel-plugin-rewrite-require'), {
aliases: {
crypto: 'crypto-browserify',
// ...
},
throwForNonStringLiteral: true,
}]
(Note: You can also do this in without these 2 js files directly in .babelrc)
(Note2: Though ReactNativify is the cleaner way, it is still giving me issues wiring up crypto.getRandomValues for production-use in RN. See this question)
You can use the rn-nodeify module to get crypto on react-native.
Add rn-nodeify to your devDependencies in package.json:
"devDependencies": {
"rn-nodeify": "^6.0.1"
}
Add the following to the scripts section of the same file:
"scripts": {
…
"postinstall": "node_modules/.bin/rn-nodeify --install crypto --hack"
}
Be aware that rn-nodeify will modify your package.json.
More information available here: https://www.npmjs.com/package/rn-nodeify
React Native packager uses Babel under the hood. This means that you can use babel-plugin-rewrite-require Babel plugin to rewrite all require('crypto') calls to require('crypto-browserify'), assuming that the latter is installed in your node_modules.
As of January 2016, you can use .babelrc file to define optional configuration, so this becomes really easy. First, install the dependencies:
npm install --save crypto-browserify
npm install --save-dev babel-plugin-rewrite-require
Then add plugins config to your .babelrc file:
{
"presets": ["react-native"],
"plugins": [
["babel-plugin-rewrite-require", {
"aliases": {
"crypto": "crypto-browserify"
}
}]
]
}
Restart the packager and that should be it.
This is the same approach that ReactNativify uses, except that here we use .babelrc instead of defining custom transformer. When ReactNativify was written, it was not supported, so they had to go with more complex solution. See this file from ReactNativify for almost complete list of node polyfills.
I was having the same issue when implementing the Twilio package in my React Native app, and having React Native break over the crypto dependency.
As a work around I ended up creating a separate, stand alone Node/Express app to act as my server and take care of the Twilio logic I had. That way I removed all Twilio logic from my React Native app and moved it to Node. I then just called my Express route in React Native using fetch, which triggered the functionality I wanted to happen with Twilio. If you're unfamiliar with fetch here's a good starting point -
Making AJAX calls with Fetch in React Native
In addition, here's my question on the crypto dependency breaking my app:
twilio-react-native-unable-to-resolve-module-crypto
As far I can see amazon-cognito-identity-js uses crypto-js 3.3.0 without any additional magic... If that version of the package works then perhaps try that.
After having tried a bunch of these solutions and never really having been satisfied with any of them (some didn't even work), I managed to stumble upon react-native-quick-crypto, which honestly worked much more effortlessly than trying to lift the existing crypto library to the front-end

Resources