Flow seems not to respect include option? - reactjs

I've setup my React project with a folder for common components I want to import directly.
src/
---components/
---common/
---/TextInput
---/TabSelector
Full folder structure from root
Each of these folders in common have a index.jsx (and other resources such as style etc) with and export default <name> statement.
So my webpack config has the following configuration:
resolve: {
modulesDirectories: [
'node_modules',
myCommonComponentsPath
]
}
which allows direct imports: import TextInput from 'TextInput'
Trying to add this to .flowconfig (according to flow's documentiation) is not working though:
[include]
./node_modules/
<PROJECT_ROOT>/src/components/common
This works with webpacks resolver (components load and work) but flow gives the following error:
9: import TextInput from 'TextInput';
^^^^^^^^^^^ TextInput. Required module not found
Any help would be appreciated.
How do I resolve this?

You need to use the module.system.node.resolve_dirname setting, which is different from the include setting. Since you are telling Webpack a new place to find modules, you also need to tell Flow about that new place.
[options]
module.system.node.resolve_dirname=node_modules
module.system.node.resolve_dirname=src/components/common
The paths are relative to the location of your '.flowconfig' file.

Related

TypeScript with Relay: Can't resolve generated module

In my MessageItem.tsx component I have the following code:
const data = useFragment(
graphql`
fragment MessageItem_message on Message {
date
body
}
`,
message as any
);
After running relay-compiler --src ./src --schema ../../schema.graphql --language typescript --artifactDirectory ./src/__generated__, a module named MessageItem_message.graphql.ts gets generated.
But when I run the app it gives me an error:
Failed to compile.
./src/components/MessageItem.tsx
Module not found: Can't resolve
'./__generated__/MessageItem_message.graphql'
The reason is only components at the src root can refer to the right path (./__generated__), whereas components in a folder actually need to refer to the path (../__generated__) but it's not doing so.
How can I configure the path?
Edit .babelrc to point to the artifactDirectory
// .babelrc
{
"plugins": [
[
"relay",
{
"artifactDirectory": "./src/ui/graphql/types"
}
]
]
}
Remove "--artifactDirectory ./src/__generated__" from the relay-compiler options.
By default it seems the Relay compiler puts a "__generated__" directory in the directory with any source code containing GraphQL.
As a result any "./__generated__" references anywhere and at any level in the source code hierarchy now work as they should.
Thanks to #ThibaultBoursier for the pointer.
PS I wonder if the --artifcactDirectory option is just meant to be used to change the name of the artifact directory, rather than its location?
Just moments ago I ran into the same issue. The reason is that the relay-compiler is using the artifactDirectory setting to decide where to put the generated files, but the babel-plugin-relay exposing the graphql tag needs to get the very same argument otherwise it just attempts to include a colocated relative file.
I fixed it in my case by configuring the plugin with a babel-plugin-macros.config.js file as follows (where the artifactDirectory is the same as the one supplied to the relay-compiler):
module.exports = {
relay: {
artifactDirectory: "./src/ui/graphql/types",
},
};
This solution assumes you are using the macro via babel-plugin-macros, otherwise you might need to supply that argument via the .babelrc file but I have no experience with that unfortunately.

Shared component library best practices

I am creating a shareable React component library.
The library contains many components but the end user may only need to use a few of them.
When you bundle code with Webpack (or Parcel or Rollup) it creates one single file with all the code.
For performance reasons I do not want to all that code to be downloaded by the browser unless it is actually used.
Am I right in thinking that I should not bundle the components? Should the bundling be left to the consumer of the components?
Do I leave anything else to the consumer of the components? Do I just transpile the JSX and that's it?
If the same repo contains lots of different components, what should be in main.js?
This is an extremely long answer because this question deserves an extremely long and detailed answer as the "best practice" way is more complicated than just a few-line response.
I've maintained our in-house libraries for 3.5+ years in that time I've settled on two ways I think libraries should be bundled the trade-offs depend on how big your library is and personally we compile both ways to please both subsets of consumers.
Method 1: Create an index.ts file with everything you want to be exposed exported and target rollup at this file as its input. Bundle your entire library into a single index.js file and index.css file; With external dependencies inherited from the consumer project to avoid duplication of library code.
(gist included at bottom of example config)
Pros: Easy to consume as project consumers can import everything from the root relative library path import { Foo, Bar } from "library"
Cons: This will never be tree shakable, and before people say to do this with ESM and it will be tree shakeable. NextJS doesn't support ESM at this current stage and neither do a lot of project setups that's why it's still a good idea to compile this build to just CJS. If someone imports 1 of your components they will get all the CSS and all the javascript for all your components.
Method 2: This is for advanced users: Create a new file for every export and use rollup-plugin-multi-input with the option "preserveModules: true" depending on how what CSS system you're using your also need to make sure that your CSS is NOT merged into a single file but that each CSS file requires(".css") statement is left inside the output file after rollup and that CSS file exists.
Pros: When users import { Foo } from "library/dist/foo" they will
only get the code for Foo, and the CSS for Foo, and nothing more.
Cons: This setup involves the consumer having to handle node_modules
require(".css") statements in their build configuration with NextJS
this is done with next-transpile-modules npm package.
Caveat: We use our own babel plugin you can find it here: https://www.npmjs.com/package/babel-plugin-qubic to allow people to import { Foo, Bar } from "library" and then with babel transform it to...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"
We have multiple rollup configurations where we actually use both methods; so for library consumers who don't care for tree shaking can just do "Foo from "library" and import the single CSS file, and for library consumers who do care for tree shaking and only using critical CSS they can just turn on our babel plugin.
Rollup guide for best practice:
whether you are using typescript or not ALWAYS build with "rollup-plugin-babel": "5.0.0-alpha.1"
Make sure your .babelrc looks like this.
{
"presets": [
["#babel/preset-env", {
"targets": {"chrome": "58", "ie": "11"},
"useBuiltIns": false
}],
"#babel/preset-react",
"#babel/preset-typescript"
],
"plugins": [
["#babel/plugin-transform-runtime", {
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "^7.8.3"
}],
"#babel/plugin-proposal-class-properties",
"#babel/plugin-transform-classes",
["#babel/plugin-proposal-optional-chaining", {
"loose": true
}]
]
}
And with the babel plugin in rollup looking like this...
babel({
babelHelpers: "runtime",
extensions,
include: ["src/**/*"],
exclude: "node_modules/**",
babelrc: true
}),
And your package.json looking ATLEAST like this:
"dependencies": {
"#babel/runtime": "^7.8.3",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"regenerator-runtime": "^0.13.3"
},
"peerDependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0",
}
And finally your externals in rollup looking ATLEAST like this.
const makeExternalPredicate = externalArr => {
if (externalArr.length === 0) return () => false;
return id => new RegExp(`^(${externalArr.join('|')})($|/)`).test(id);
};
//... rest of rollup config above external.
external: makeExternalPredicate(Object.keys(pkg.peerDependencies || {}).concat(Object.keys(pkg.dependencies || {}))),
// rest of rollup config below external.
Why?
This will bundle your shit to automatically to inherit
react/react-dom and your other peer/external dependencies from the
consumer project meaning they won't be duplicated in your bundle.
This will bundle to ES5
This will automatically require("..") in all the babel helper functions for objectSpread, classes, etc FROM the consumer project which will wipe another 15-25KB from your bundle size and mean that the helper functions for objectSpread won't be duplicated in your library output + the consuming projects bundled output.
Async functions will still work
externals will match anything that starts with that peer-dependency suffix i.e babel-helpers will match external for babel-helpers/helpers/object-spread
Finally here is a gist for an example single index.js file output rollup config file.
https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3
Where the target src/export/index.ts looks like this...
export { Button } from "../components/Button/Button";
export * from "../components/Button/Button.styles";
export { Checkbox } from "../components/Checkbox/Checkbox";
export * from "../components/Checkbox/Checkbox.styles";
export { DatePicker } from "../components/DateTimePicker/DatePicker/DatePicker";
export { TimePicker } from "../components/DateTimePicker/TimePicker/TimePicker";
export { DayPicker } from "../components/DayPicker/DayPicker";
// etc etc etc
Let me know if you experience any problems with babel, rollup, or have any questions about bundling/libraries.
When you bundle code with Webpack (or Parcel or Rollup) it creates one single file with all the code.
For performance reasons I do not want to all that code to be downloaded by the browser unless it is actually used
It's possible to have separate files generated for each component. Webpack has such ability by defining multiple entries and outputs. Let's say you have the following structure of a project
- my-cool-react-components
- src // Folder contains all source code
- index.js
- componentA.js
- componentB.js
- ...
- lib // Folder is generated when build
- index.js // Contains components all together
- componentA.js
- componentB.js
- ...
Webpack file would look something like this
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
componentA: './src/componentA.js',
componentB: './src/componentB.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'lib'),
},
};
More info on "code splitting" is here in Webpack docs
If the same repo contains lots of different components, what should be in main.js?
There is a single field in package.json file named main, it's good to put its value lib/index.js according to the project structure above. And in index.js file have all components exported. In case consumer wants to use single component it's reachable by simply doing
const componentX = require('my-cool-react-components/lib/componentX');
Am I right in thinking that I should not bundle the components? Should the bundling be left to the consumer of the components? Do I leave anything else to the consumer of the components? Do I just transpile the JSX and that's it?
Well, it's up to you. I've found that some React libraries are published in original way, others - are in bundled way. If you need some build process, then define it and export bundled version.
Hope, all your questions are answered :)
You can split your components like lodash is doing for their methods.
What you probably have is separate components that you could allow importing separately or through the main component.
Then the consumer could import the whole package
import {MyComponent} from 'my-components';
or its individual parts
import MyComponent from 'my-components/my-component';
Consumers will create their own bundles based on the components they import. That should prevent your whole bundle being downloaded.
You should take a look at Bit, I think this is a good solution to share, reuse and visualize components.
It is very easy to setup. You can install your bit library or just a component with:
npm i #bit/bit.your-library.components.buttons
Then you can import the component in your app with:
import Button3 from '#bit/bit.your-library.components.buttons';
The good part is that you don't have to worry about configuring Webpack and all that jazz. Bit even supports the versioning of your components. This example shows a title-list react component so you can take a look if this meets your requirements or not
There is a configuration in webpack to create chunk files. To start with it will create the main bundle into multiple chunks and get it loaded as when required. if your project has well structured modules, it will not load any code which is not required.

Flow required module not found

Using Webpack 2, Flow 0.46.0
I have a pretty large app I am developing, so am using Webpack resolve modules to create alias import names e.g. '../../../../constants/ServiceURI' to 'constants/ServiceURI'
Everything works fine until I add flow. There must be a way to use mapper or resolve_dirname to fix this, but I cannot figure out how. No matter what I do it breaks flow.
I really want to use flow, but this is a blocker for me.
Project structure:
./flowconfig
./webpack.config.js
./src
/js
/constants
/actions
/...
/css
Webpack config looks like:
resolve: {
modules: [
path.resolve('./src/js'),
path.resolve('./src/js/constants'),
'node_modules'
],
extensions: ['.js', '.jsx']
Flow config looks like:
[ignore]
.*/node_modules/*
[include]
<PROJECT_ROOT>/src/js/
[libs]
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
esproposal.decorators=ignore
# Tried this
module.name_mapper='^constants$' -> '<PROJECT_ROOT>/src/js/constants'
# Tried using this too
module.system.node.resolve_dirname=./src/js
module.system=haste
munge_underscores=true
[version]
0.46.0
Flow Error:
rc/js/actions/ActionActivity.js:6
6: import { ACTIVITY_API } from 'constants/ServiceURI'
^^^^^^^^^^^^^^^^^^^^^^
constants/ServiceURI. Required module not found
The regular expression you're using in the module.name_mapper does not match constants/ServiceURI. It only matches exactly constants. You additionally need to match anything that comes after constants to be able to resolve all modules inside the constants directory.
module.name_mapper='^constants/\(.*\)$' -> '<PROJECT_ROOT>/src/js/constants/\1'
Where \( and \) create a capturing group (the slashes are required), which you can refer to as \1. For more information see module.name_mapper.

Importing self-created libraries in reactjs

I'm using React and ES6 using babel and webpack. I am very new to this ecosystem.
I am trying to import some common utility functions into my jsx file but react is unable to find the file
homepage.jsx
var pathToRoot = './../..';
import path from 'path';
import React from 'react';
import ReactDOM from 'react-dom';
var nextWrappedIndex = require(path.join(pathToRoot,'/lib/utils.js')).nextWrappedIndex;
//some react/JSX code
utils.js
var nextWrappedIndex = function(dataArray) {
//some plain js code
return newIndex;
}
exports.nextWrappedIndex = nextWrappedIndex;
Directory structure is as follows:
src
|--app.js
|--components
| |--homepage
| |--homepage.jsx
|
|--lib
| |--utils.js
I am on a windows 10 machine and was facing issues during compilation providing the path by any other means. Using path.join solved compilation issue but the browser while rendering throws this error
Uncaught Error: Cannot find module '../../lib/utils.js'.
How do I accomplish this?
Also, is this the best way to do it(if altogether it is way it is supposed to be done in such ecosystem)?
One of the best and easiest way I have found in such a setup is to use Webpack aliases.
Webpack aliases will simply associate an absolute path to a name that you can use to import the aliased module from anywhere. No need to count "../" anymore.
How to create an alias?
Let's imagine that your Webpack config is in the parent folder of your src folder.
You would add the following resolve section in your config.
const SRC_FOLDER = path.join(__dirname, 'src')
resolve: {
alias: {
'my-utils': path.join(SRC_FOLDER, 'lib', 'utils')
}
}
Now, anywhere in your app, in any of your modules or React component you can do the following:
import utils from 'my-utils'
class MyComponent extends React.component {
render () {
utils.doSomething()
}
}
Small note about this method. If you run unit tests with a tool like enzyme and you don't run the component tested through Webpack, you will need to use the babel-plugin-webpack-alias.
More info on Webpack website: Webpack aliases
I solved this by replacing
var nextWrappedIndex = require(path.join(pathToRoot,'/lib/utils.js')).nextWrappedIndex;
with
import nextWrappedIndex from './../../lib/utils.js';
I tried to reproduce your code and Webpack printed me the following error:
WARNING in ./app/components/homepage/homepage.jsx
Critical dependencies:
50:0-45 the request of a dependency is an expression
# ./app/components/homepage/homepage.jsx 50:0-45
It means that Webpack couldn't recognize your require() expression because it works only with static paths. So, it discourages the way you are doing.
If you would like to avoid long relative paths in your import, I'd recommend you to set up Webpack.
First, you can set up aliases per Amida's answer.
Also, you can set up an extra module root via resolve.modules to make webpack look into your src folder, when you are importing something absolute, like lib/utils.js

Import Highcharts and highcharts-more (AngularJS 1.5 + TypeScript + webpack)

I'm trying to use Highcharts with some of its extensions (like "highcharts-more") in a project that uses webpack, TypeScript and AngularJS (version 1.5).
I've installed Highcharts through npm (https://www.npmjs.com/package/highcharts), but I'm not able to import the extensions that come with it.
The actual trick I'm doing is to set some global variables in the webpack config file
plugins: [
new webpack.ProvidePlugin({
Highcharts: 'highcharts',
HighchartsMore: 'highcharts/highcharts-more',
HighchartsExporting: 'highcharts/modules/exporting'
})
]
and extending Highcharts manually
HighchartsMore(Highcharts);
HighchartsExporting(Highcharts);
without any import in between. With this non-ideal solution TypeScript is complaining because
error TS2304: Cannot find name 'HighchartsMore'
error TS2304: Cannot find name 'HighchartsExporting'
In particular with Highcharts there is no error. Which I guess has to do with the fact that Highcharts is the only thing I manage to import, via
import * as Highcharts from 'highcharts';
which I can substitute with the Highchart global declaration in the webpack config. What I would like is to import every module in a clean way, something like
import {HighchartsMore} from 'highcharts-more';
Any idea is very much appreciated.
This type of error can occur when you do not have definition files for exported variables. Those Highcharts extensions still require them - you might want to read more about importing modules without d.ts here: https://github.com/Urigo/meteor-static-templates/issues/9 - it might change in the future.
You need to create a d.ts file for the extensions. For highcharts-more this is my file:
/// <reference path="index.d.ts" />
declare var HighchartsMore: (H: HighchartsStatic) => HighchartsStatic;
declare module "highcharts/highcharts-more" {
export = HighchartsMore;
}
reference path points to standard DefinietelyTyped Highcharts file from here https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/highcharts/highcharts.d.ts
It allows to use type from Highcharts.d.ts because initializing will need proper typing for initializing extension:
HighchartsMore(Highcharts);
And finally don't forget to include all d.ts files by defining tsconfig or writing reference path in your files.
remove these lines from webpack.config.js:
plugins: [
new webpack.ProvidePlugin({
Highcharts: 'highcharts',
HighchartsMore: 'highcharts/highcharts-more',
HighchartsExporting: 'highcharts/modules/exporting'
})
]
install typings file for highcharts using this:
npm install --save #types/highcharts
change your import statements to following:
import * as Highcharts from 'highcharts';
import HighchartsMore = require('highcharts/highcharts-more');
HighchartsMore(Highcharts);

Resources