I have questions about babel.rc config file.
I searhed and saw two different config file examples.
{
"presets": [["es2015", { "modules": false }]],
"plugins": ["syntax-dynamic-import"]
}
and
{
"presets": [
[
"env",
{
// leave imports as they are
"modules": false
}
]
],
"plugins": [
// support dynamic import syntax, but leave it unchanged
"babel-plugin-syntax-dynamic-import"
]
}
My questions are:
1)What is the difference between es2015 preset and env preset?
2)Why do we need modules option to be false?I understood that it
instructs Babel to not try and parse the imports.But why exactly do we need that?
3)And how about dynamic imports?Why do we need to use plugin?Is there any relation between modules:false option?
4)What about browser support for the dynamic imports?Can babel transform it to ES5?Can dynamic imports and code splitting work with IE10 or IE11?How can we figure that which browser supports dynamic imports and code splitting?
Ad 1 - babe-preset-es2015
This is deprecated. If you want to stay up to date, use the env preset
Note from authors:
instead of making more yearly presets 😠, Babel now has a better
preset that we recommend you use instead: npm install babel-preset-env
--save-dev. preset-env without options will compile ES2015+ down to ES5 just like using all the presets together and thus is more future
proof
Ad 2 - Modules is set to false to ensure that import statements are left as is (opposed to transpiling them to require). For example: You can do this to give Webpack the ability to statically analyze our code to produce more efficient bundles.
Ad 3 - It allows parsing of import(). I do not know if there is a relation to modules option.
Ad 4 - 'Note: Dynamic import() is available in Chrome 63 and Safari Technology Preview 24' -> source: Dynamic imports
Related
I've never published an NPM package before. All these details to generate a package seem way too complicated to my level. The only tool, that was beginner friendly, that I could find is create-react-library which recommended to switch to tsup instead.
I'm asking here to know if there's a batteries-included, most-cases-met, setup for tsup or any other tool of your recommendation for this kind of project (and I think this is a common scenario):
A React Project
Typed with Typescript
Tested with Jest
No dependencies
Exports React components
Should be public on NPM
If you want most-cases-met, batteries-included way to make a library like that then take a look at dts-cli. It will work but it will definitely be slower than tsup. I myself am in the process of switching a library with about 60 react components from dts-cli to tsup because the build time started taking too long (about a minute) on a MacBook Air M1.
Here is an example setup.
First you need to bundle each component separately. You can use a glob as an entry point in Tsup.
Keep in mind that options that works for Esbuild works for Tsup most of the time.
// tsup.config.ts
defineConfig([
{
clean: true,
sourcemap: true,
tsconfig: path.resolve(__dirname, "./tsconfig.build.json"),
entry: ["./components/core/!(index).ts?(x)"],
format: ["esm"],
outDir: "dist/",
esbuildOptions(options, context) {
// the directory structure will be the same as the source
options.outbase = "./";
},
},
Then you'll want to have a index.ts, for convenience, that expose named exports. This index is sometimes referred as a "barrel" file.
// index.ts
// the actual file is "Button.tsx" but we still want a ".js" here
export { Button } from "./components/core/Button.js";
Notice the .js extension. ESM expects explicit extensions so it's needed in the final build.
Adding the .js doesn't seem to bother TypeScript, which stills correctly recognize the type of "Button" from Button.tsx. At this point I am not sure why it works, but it does.
Transpile this index, without bundling.
// tsup.config.ts
{
clean: true,
sourcemap: true,
tsconfig: path.resolve(__dirname, "./tsconfig.build.json"),
entry: ["index.ts", "./components/core/index.ts"],
bundle: false,
format: ["esm"],
outDir: "dist",
esbuildOptions(options, context) {
options.outbase = "./";
},
},
])
Finally define your package.json as usual:
"sideEffects": false,
"type": "module",
"exports": {
".": "./dist/index.js"
},
sideEffects is a non-standard property targeting application bundlers like Webpack and Rollup.
Setting it to false tells them that the package is safe for tree-shaking.
Now import { Button } from "my-package" should work as you expect, and tree-shaking and dynamic loading at app-level become possible because "Button" is bundled as its own ES module Button.js, and the package is marked as being side-effect free.
This is confirmed by my Webpack Bundle Analyzer in a Next app:
Before (a single bundled index.js):
After (separate files means I can select more precisely my imports):
Final config available here (might be improved in the future)
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.
Problem
create-react-app v2+ supports TypeScript and CSS Modules out of the box... separately. The problem arises when you try to use the two together. Facebook had an extensive discussion about this issue and ultimately closed it off on GitHub. So developers have to use hacks and other workarounds to get these two technologies to play nicely together alongside CRA.
Existing workaround:
You can manually create ComponentName.module.css.d.ts files with type definitions like this: export const identifierName: string. This allows you to take advantage of TypeScript's typing and VS Code's auto-complete when you go to import ComponentName.module.css. Unfortunately, this is extremely tedious.
Solution (?):
The folks over at Dropbox created typed-css-modules-webpack-plugin to address this issue; it auto-genertes those *.d.ts files for you. They show how to install it with yarn or npm and then give this minimal code example:
const path = require('path');
const {TypedCssModulesPlugin} = require('typed-css-modules-webpack-plugin');
module.exports = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
},
{
test: /\.css$/,
use: [
'style-loader',
// Use CSS Modules
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
],
},
// Generate typing declarations for all CSS files under `src/` directory.
plugins: [
new TypedCssModulesPlugin({
globPattern: 'src/**/*.css',
}),
],
};
Unfortunately, it's not immediately clear how I can use this with create-react-app. I'm not very knowledgeable when it comes to Webpack, and I'm using customize-cra to avoid ejecting out of create-react-app so I can customize the Webpack configs for some things I need. For example, Ant Design lets you import components on demand by using babel-plugin-import as detailed here:
https://ant.design/docs/react/use-in-typescript#Use-babel-plugin-import
Question: How can I convert the above Webpack configuration code to a customize-cra equivalent so that I don't have to eject out of CRA?
Okay, so I eventually did figure this out, and I wrote a blog post on the subject for anyone who runs into a similar issue:
https://aleksandrhovhannisyan.github.io/blog/dev/how-to-set-up-react-typescript-ant-design-less-css-modules-and-eslint/#3-create-react-app-css-modules-and-typescript-
The solution uses the typescript-plugin-css-modules plugin. Here are the relevant bits from my blog post:
yarn add -D typescript-plugin-css-modules
After it’s installed, add the plugin to your tsconfig.json:
{
"compilerOptions": {
"plugins": [{ "name": "typescript-plugin-css-modules" }]
}
}
Next, create a file named global.d.ts under your src directory. You don’t have to name it global, by the way; you can name the file whatever you want, as long as it has the .d.ts extension. Enter these contents:
declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
If you want to also use SASS or CSS, simply add more module declarations and change the .less extension.
We’re almost done! Per the plugin’s usage instructions, if you want intellisense to work in VS Code, you’ll need to force VS Code to use your workspace version of TypeScript instead of the globally installed version. Remember when we installed TypeScript via CRA at the very beginning? That’s our workspace version of TypeScript.
Here’s how to use the workspace version of TypeScript in VS Code:
Open any TypeScript file.
Click the version number on the blue status bar at the bottom of VS Code.
Select Use Workspace Version (3.7.3 as of this writing).
Here’s a screenshot to make that clearer:
Once you do that, VS Code will create a .vscode directory in your project for workspace settings.
At this point, you're all set to use CSS Modules with TypeScript.
UPDATE 2022
Note: If you're using react-scripts#2.1.x or higher you don't need to use custom definitions like
declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
Custom definitions
Note: Create React App users can skip this section if you're using react-scripts#2.1.x or higher.
Also you can add this VS code setting to you local JSON settings file:
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
This will ensure that VS Code will use the project’s version of Typescript instead of the VS Code version and will prompt you to do so if you aren’t already.
Well, everything is correct as said AlexH.
1 in tsconfig.ts.
{
"compilerOptions": {
"plugins": [{ "name": "typescript-plugin-css-modules" }]
}
}
2 in global.d.ts
declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
But Also in tsconfig you should write
"include": [
"global.d.ts",
...
]
i keep getting the error:
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Duplicate plugin/preset detected.
If you'd like to use two separate instances of a plugin,
they need separate names, e.g.
plugins: [
['some-plugin', {}],
['some-plugin', {}, 'some unique name'],
]
this is my babelrc with the changes it is asking for:
{
"presets": [
["#babel/env"],
["#babel/preset-react"]
],
"plugins": [
["#babel/plugin-syntax-jsx"],
["#babel/plugin-transform-react-jsx"],
["#babel/plugin-transform-react-display-name"],
["#babel/plugin-transform-react-jsx-self"],
["#babel/plugin-transform-react-display-name"]
]
}
Not really sure where i have the wrong syntax for the file. Also this is my first time configuring webpack4 with babel for a react application. Please let me know if everything looks fine for this to work with react.
Like the error says: you have a duplicate. ["#babel/plugin-transform-react-display-name"] is in your "plugins" array twice. Just delete one of them.
With that said: take a look at what's already included in preset-react (a preset is a pre-defined bundle of plugins). All of those plugins are already included (though "plugin-transform-react-jsx-self" is behind an option.)
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.