In Rollup, create ESM module with no babel transpiling except Flow - javascript

I'm trying to release a library that contains Flow syntax in several forms, using Rollup and Babel:
UMD, ES5 (works)
UMD, ES5, minified (works)
ES module (problem)
Specifically, I want the ES module version to be as minimally transpiled as possible: it should use the full range of ES6 features just as I wrote them. I can't seem to stop Babel transpiling down to ES5 though - it creates an _objectSpread2() function and replaces every spread operator with it.
The only reason I want to use Babel is for the Flow syntax. Specifically, converting class property syntax:
class Foo {
myProp = 1;
}
(The standalone tool flow-remove-types does not transform class syntax, so its output is not valid JS).
This is the Rollup config that currently over-transpiles, to ES5:
import { nodeResolve } from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import flow from 'rollup-plugin-flow';
export default [
{
input: 'src/index.js',
output: [
{
file: 'dist/index.esm.js',
format: 'esm', // ES2015 modules version so consumers can tree-shake
},
],
plugins: [flow(), commonjs(), nodeResolve()],
},
];
Even when I remove the presets, it still does exactly the same thing:
import babel from '#rollup/plugin-babel';
export default [
{
input: 'src/index.js',
output: [
{
file: 'dist/index.esm.js',
format: 'esm', // ES2015 modules version so consumers can tree-shake
},
],
plugins: [
babel({
presets: [],
}),
],
},
];
Do I need to change something in .babelrc? It is currently as follows:
{
"presets": [
[
"#babel/preset-env",
{
"targets": {
"esmodules": true
}
}
],
["#babel/preset-flow"]
]
}
The library is here.

Hmm, I think the problem was that .babelrc was being applied even in the ESM case. So the answer is:
Remove the .babelrc file
Move its configuration to rollup.config.js
Configure rollup-esm.config.js to explicitly handle class syntax.
Rollup.config.js:
...
plugins: [
flow(),
commonjs(),
nodeResolve(),
babel({
babelHelpers: 'bundled',
presets: [
[
'#babel/preset-env',
{
targets: {
esmodules: true,
},
},
],
['#babel/preset-flow'],
],
}),
],
rollup-esm.config.js:
import babel from '#rollup/plugin-babel';
export default [
{
input: 'src/index.js',
output: [
{
file: 'dist/index.esm.js',
format: 'esm', // ES2015 modules version so consumers can tree-shake
},
],
plugins: [
babel({
plugins: ['#babel/plugin-proposal-class-properties'],
presets: ['#babel/preset-flow'],
}),
],
},
];

Related

Library built with Webpack 5 not tree shakeable

I have a React component library that is built by webpack 5.75.0. The library exports 2 components:
// src/index.ts
export { default as ComponentA } from "components/ComponentA";
export { default as ComponentB } from "components/ComponentB";
A simple CRA app uses this library and imports only ComponentA. But when analyzing the bundle I see that ComponentB's dependencies are in it and have not been tree-shaken.
The parent app is just a CRA app with no config overrides. Tree shaking works there for other modules (MUI, lodash-es, etc), so the problem is with the my library.
I seem to have followed all requirements: in my package.json I do have
"sideEffects": false,
"main": "lib/index.js",
"module": "lib/index.js",
Here's my babel config:
module.exports = {
presets: [
["#babel/preset-env", { targets: { node: "current", modules: false } }],
"#babel/preset-typescript",
[
"#babel/preset-react",
{
runtime: "automatic",
},
],
],
plugins: [
"#babel/plugin-syntax-jsx",
[
"#babel/plugin-transform-runtime",
{
regenerator: true,
useESModules: true,
},
],
],
};
And here's my webpack config:
const config = {
mode: "production",
devtool: false,
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "lib"),
filename: "index.js",
module: true,
library: {
type: "module",
},
},
plugins: [
//...
],
module: {
rules: [
//...
],
},
resolve: {
//...
},
externals: [
"react",
"react-dom",
],
experiments: {
outputModule: true,
},
optimization: {
minimize: false,
},
};
But still, the ComponentB's dependencies are showing up in the bundle of the CRA app that uses this library.
Am I missing something? Is there some official example of how to configure tree-shaking library with webpack 5?
Thanks.
P.S. My question relates to this question, but since 2021 it's become possible to build ES modules with Webpack 5.

How to extract all used polyfill from multiple libs by rollup?

I use rollup to create multiple libs, rollup config file as below:
import terser from '#rollup/plugin-terser';
import { nodeResolve } from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import { babel } from '#rollup/plugin-babel';
import eslint from '#rollup/plugin-eslint';
const distDir = "dist/libs"
const api = {
input: "src/libs/api/index.js",
external: ["axios"],
output: [
{
file: `${distDir}/api.umd.js`,
format: "umd",
name: 'Api',
globals: {axios: 'axios'}
},
{
file: `${distDir}/api.es.js`,
format: "es"
}
],
plugins: [
terser(),
babel({babelHelpers: 'runtime', exclude: 'node_modules/**'}),
nodeResolve({
browser:true,
}),
commonjs(),
eslint({
throwOnError: true,
throwOnWarning: true,
include: ['src/**'],
exclude: ['node_modules/**']
})
]
}
const test = {
...
}
export default ()=>{
return [
api, // it contains some polyfill
test // it also contains some polyfill
]
};
My .babelrc is:
{
"presets": [
["#babel/preset-env", {
"corejs": {"version": "3", "proposals": false},
"useBuiltIns": "usage"
}
]
],
"plugins": [
[
"#babel/plugin-transform-runtime"
]
]
}
Now, api and test contain some polyfill. Is there a way to extract the used polyfills of all modules to form a single umd file but not Full Function polyfill.Thank you.

How to configure Storybook to run from a directory other than the project root

I'm trying to configure Storybook to run from a directory that is not the root of the project and I'm having a little trouble. I've setup a mono-rep using https://github.com/jibin2706/cra-monorepo-demo as base.
My project directory looks like this:
- project
-- packages
---- app
---- components
---- utils
---- stories
------ .storybook
-------- main.js
------ ComponentA
-------- ComponentA.stories.mdx
Because I'm using a monorep with aliases (e.g. a component can import from #project/utils) I've configured webpack in .storybook/main.js to read like:
const path = require('path');
module.exports = {
stories: ['../**/*.stories.mdx', '../../**/*.stories.#(js|jsx|ts|tsx)'],
addons: [
'#storybook/addon-links',
'#storybook/addon-essentials',
'#storybook/preset-create-react-app',
],
webpackFinal: async (config, { configType }) => {
const result = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
'#project/components': path.resolve(
process.cwd(),
'packages/components'
),
},
},
};
},
};
Then within my ComponentA.stories.mdx I have an import like import { ComponentA } from '#project/components';
When I run this however, I'm always hitting an error when it encounters JSX within a .js file:
ERROR in ./packages/components/MyComponent1/MyComponent1.js 106:11
Module parse failed: Unexpected token (106:11)
File was processed with these loaders:
./node_modules/#pmmmwh/react-refresh-webpack-plugin/loader/index.js
You may need an additional loader to handle the result of these loaders.
|
return <React.Fragment>{children}</React.Fragment>;
I can't seem to work out why this error is throwing. I've tried running with yarn storybook --debug-webpack which seems to include a loader for both jsx and js files. I'm not 100% sure if this is correct, but it looks roughly right from other docs I've read.
module: {
rules: [
{
test: /\.(mjs|tsx?|jsx?)$/,
use: [
{
loader: '/home/ian/src/cra-monorepo-demo/node_modules/babel-loader/lib/index.js',
options: {
sourceType: 'unambiguous',
presets: [
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/preset-env/lib/index.js',
{ shippedProposals: true, loose: true }
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/preset-typescript/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/preset-react/lib/index.js'
],
plugins: [
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-shorthand-properties/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-block-scoping/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-decorators/lib/index.js',
{ legacy: true }
],
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-class-properties/lib/index.js',
{ loose: true }
],
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-private-methods/lib/index.js',
{ loose: true }
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-export-default-from/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-syntax-dynamic-import/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-object-rest-spread/lib/index.js',
{ loose: true, useBuiltIns: true }
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-classes/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-arrow-functions/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-parameters/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-destructuring/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-spread/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-for-of/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#storybook/core-common/node_modules/babel-plugin-macros/dist/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-optional-chaining/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-nullish-coalescing-operator/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/babel-plugin-polyfill-corejs3/lib/index.js',
{
method: 'usage-global',
absoluteImports: '/home/ian/src/cra-monorepo-demo/node_modules/core-js/index.js',
version: '3.16.1'
}
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-template-literals/lib/index.js'
]
}
}
],
include: [ '/home/ian/src/cra-monorepo-demo' ],
exclude: [ /node_modules/, /dist/ ]
},
{
test: /\.js$/,
use: [
{
loader: '/home/ian/src/cra-monorepo-demo/node_modules/babel-loader/lib/index.js',
options: {
sourceType: 'unambiguous',
presets: [
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/preset-env/lib/index.js',
{
shippedProposals: true,
modules: false,
loose: true,
targets: 'defaults'
}
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/preset-react/lib/index.js'
],
plugins: [
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-shorthand-properties/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-block-scoping/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-decorators/lib/index.js',
{ legacy: true }
],
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-class-properties/lib/index.js',
{ loose: true }
],
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-private-methods/lib/index.js',
{ loose: true }
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-export-default-from/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-syntax-dynamic-import/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-object-rest-spread/lib/index.js',
{ loose: true, useBuiltIns: true }
],
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-classes/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-arrow-functions/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-parameters/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-destructuring/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-spread/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-transform-for-of/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#storybook/core-common/node_modules/babel-plugin-macros/dist/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-optional-chaining/lib/index.js',
'/home/ian/src/cra-monorepo-demo/node_modules/#babel/plugin-proposal-nullish-coalescing-operator/lib/index.js',
[
'/home/ian/src/cra-monorepo-demo/node_modules/babel-plugin-polyfill-corejs3/lib/index.js',
{
method: 'usage-global',
absoluteImports: '/home/ian/src/cra-monorepo-demo/node_modules/core-js/index.js',
version: '3.16.1'
}
]
]
}
}
],
include: [Function: include]
},
...
Can anyone see what I might be missing here, or what additional config is required?
The issue is probably with the Storybook project root. The default babel-loader defines an include that is equal to the project root. And the "project root" is usually the closest .git folder.
A workaround is to set the correct project root:
const path = require("path");
module.exports = {
// ...
webpackFinal: async (config, { configType }) => {
const babelLoaderRule = config.module.rules.find(
(rule) => rule.test.toString() === /\.(mjs|tsx?|jsx?)$/.toString()
);
// set correct project root
babelLoaderRule.include = [path.resolve(__dirname, "../..")];
return config;
}
};
What "correct" path is, depends on your setup.
Check my post for a longer write-up.
As far as I can tell your .storybook/main.js looks fine.
The built-in loader should also work as expected.
Concerning the error you're seeing: have you tried changing the file-type (aka renaming the file ending) from .js to .jsx?
Because the loader can discern between .js and .jsx files, however the interpreter cannot comprehend jsx-notation if it's not explicitely told to do so (as is the case with your .js files).

How to properly configure babel for material-ui in Next.js?

DOCS:
https://material-ui.com/guides/minimizing-bundle-size/#development-environment
"Create a .babelrc.js file in the root directory of your project:
const plugins = [
[
'babel-plugin-transform-imports',
{
'#material-ui/core': {
// Use "transform: '#material-ui/core/${member}'," if your bundler does not support ES modules
'transform': '#material-ui/core/esm/${member}',
'preventFullImport': true
},
'#material-ui/icons': {
// Use "transform: '#material-ui/icons/${member}'," if your bundler does not support ES modules
'transform': '#material-ui/icons/esm/${member}',
'preventFullImport': true
}
}
]
];
module.exports = {plugins};"
https://nextjs.org/docs/advanced-features/customizing-babel-config
"To add presets/plugins with custom configuration, do it on the next/babel preset like so:
{
"presets": [
[
"next/babel",
{
"preset-env": {},
"transform-runtime": {},
"styled-jsx": {},
"class-properties": {}
}
]
],
"plugins": []
}"
QUESTION:
How to properly configure babel for material-ui in Next.js ? My implementation below is apparently incorrect as import { ConstructionOutlined } from '#material-ui/icons';is still causing very long load times in dev mode. I observed no error messages when trying the below implementation and variations.
CODE:
{
"presets": [
[
"next/babel",
{
"babel-plugin-transform-imports":
{
"#material-ui/core": {
// Use "transform: '#material-ui/core/${member}'," if your bundler does not support ES modules
"transform": "#material-ui/core/esm/${member}",
"preventFullImport": true
},
"#material-ui/icons": {
// Use "transform: '#material-ui/icons/${member}'," if your bundler does not support ES modules
"transform": "#material-ui/icons/esm/${member}",
"preventFullImport": true
}
}
}
]
],
"plugins": []
}
OR
module.exports = {
presets: [
["next/babel"]
],
plugins: [
[
'babel-plugin-import',
{
'libraryName': '#material-ui/core',
// Use "'libraryDirectory': ''," if your bundler does not support ES modules
'libraryDirectory': 'esm',
'camel2DashComponentName': false
},
'core'
],
[
'babel-plugin-import',
{
'libraryName': '#material-ui/icons',
// Use "'libraryDirectory': ''," if your bundler does not support ES modules
'libraryDirectory': 'esm',
'camel2DashComponentName': false
},
'icons'
],
]
}
OR ELSE ?
I could exactly understand your problem. Follow this.
npm install babel-plugin-import --save-dev
Create a .babelrc file in the root directory of your next.js project with the following content:
{
"presets": ["next/babel"],
"plugins": [
[
'babel-plugin-import',
{
libraryName: '#mui/material',
libraryDirectory: '',
camel2DashComponentName: false,
},
'core',
],
[
'babel-plugin-import',
{
libraryName: '#mui/icons-material',
libraryDirectory: '',
camel2DashComponentName: false,
},
'icons',
],
]
}
Restart your development server.
This above babel configuration will convert
// from
import { Button, TextField } from '#mui/material'; ( great developer experience)
// to
import Button from '#mui/material/Button'; (smaller bundle size means great user experience)
import TextField from '#mui/material/TextField';
As a result, you will notice
faster loading of development server.
smaller bundle size
also faster client navigation with next/link and fallback:true.
Source: Babel config docs
Mui
Next.js
Hope it works for you too!
Adding babel-plugin-transform-imports is what worked for me. My .babelrc file looks like this:
{
"presets": ["next/babel"],
"plugins": [
[
"babel-plugin-transform-imports",
{
"#material-ui/core": {
"transform": "#material-ui/core/${member}",
"preventFullImport": true
},
"#material-ui/icons": {
"transform": "#material-ui/icons/${member}",
"preventFullImport": true
}
}
]
]
}
Try using the non-esm version that you have commented out in your implementation. After doing that the build time dropped significantly for me. I did have to update styles imports for Material UI like they recommend in their documentation as well.

Is it safe to mark prop-types as peer dependency in your react module

How can I export my component's propTypes so when I include this package in my project, IDE can suggest them without affecting my bundle size ?
Or how the user may be able to get rid of them ?
Is it safe to mark prop-types as peeDependency since react includes it ?
I use rollup#1.27.13 and #babel/core#7.7.7 and few other plugins for each of them.
When I remove propTypes using the code bellow, my bundle size is 1.58kb gzipped
{
"presets": [
"#babel/preset-react",
["#babel/preset-env", {
"modules": false
}]
],
"plugins": [
["transform-react-remove-prop-types", {
"removeImport": true
}],
"#babel/plugin-proposal-optional-chaining"
]
}
But when I leave them (and then I can get suggestions by IDE) it jumps to 6.45kb gzipped.
rollup config:
{
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'cjs'
},
{
file: pkg.module,
format: 'es'
}
],
plugins: [
external(),
babel({
exclude: 'node_modules/**'
}),
resolve(),
commonjs(),
terser()
],
}
Thanks

Categories

Resources