Rollup config for React component library not working with SSR - javascript

I'm trying to set up a React component library using Rollup, which my React app then installs and uses. The consuming React application is being rendered on the server. I have managed to get this set up working with Webpack, wherein I'm bundling my React component library into one single file and sending it down. Problem is this is a huge file and negates the point of SSR (part of it at least) as it takes a long time to download.
So I decided to use Rollup to split up the components into individual ES modules which are then pulled in and used as required by the app. The problem is that I can't get this to work with SSR at all. This is my first time using Rollup so I may have missed something obvious. Here is my rollup.config.js
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import css from 'rollup-plugin-css-only';
import alias from '#rollup/plugin-alias';
import image from '#rollup/plugin-image';
import json from '#rollup/plugin-json';
import replace from '#rollup/plugin-replace';
import namedDeps from './named-dependencies';
import stylus from './rollup-plugin-stylus-v2';
import postcssUrl from './rollup-plugin-postcss-url';
import autoExternal from 'rollup-plugin-auto-external';
import url from '#rollup/plugin-url';
import fs from 'fs';
const namedDepsObject = namedDeps
.map(depPath => ({
keys: Object.keys(require(depPath)).filter(v => v !== 'default'),
name: depPath,
}))
.reduce((acc, val) => {
acc[val.name] = val.keys;
return acc;
}, {});
let ComponentsList = require('./components-file'); // This has an array of component src and name
const rollConfigObject = fileName => {
const configOpts = {
input: `${fileName.src}`,
output: [
{
file: `dist/ssr/esm/${fileName.name}.js`,
format: 'esm',
}
],
plugins: [
autoExternal(),
url({
include: ['**/*.svg', '**/*.png', '**/*.jpg', '**/*.gif', '**/*.woff', '**/*.woff2', '**/*.eot', '**/*.ttf', ],
emitFiles: false
}),
babel({
exclude: 'node_modules/**',
presets: ['#babel/env', '#babel/react'],
plugins: ['#babel/plugin-syntax-dynamic-import'],
runtimeHelpers: true,
}),
resolve(),
commonjs({
sourceMap: false,
namedExports: {
...namedDepsObject,
},
}),
image(),
json(),
stylus(),
postcssUrl(),
css({
output: function(styles, styleNodes){
fs.appendFileSync(__dirname + '/dist/ssr/styles.css', styles);
}
}),
],
};
return { ...configOpts };
};
export default ComponentsList.map(
moduleName => {
return rollConfigObject(moduleName);
}
)
Now this creates the separate component bundles like I want, but the problem is that it seems to be including code within the createCommonjsModule methods from node_modules which is then injecting browser objects like document, which when I attempt to render on the server throws an error.
I am trying to not include node_modules at all, so when I try to run this with only the babel plugin, it throws an Error: Could not resolve entry module and refuses to build. An example is Braintree, it's adding a lot of code with document usage.
I'm not sure how to get it to not inject node_modules code within the component. In the app, I'm using Webpack to run these modules through babel, but objects like document will pass through and then refuse to work on the server.
Any suggestions would be great here, been struggling with this for a number of days now. Thanks!

I got the config to work after I updated my babel setting to
babel({
exclude: [
'../../node_modules/**',
'../node_modules/**',
'node_modules/**',
],
presets: [
[
'#babel/env',
{
modules: false,
targets: {
esmodules: true,
},
},
],
'#babel/react',
],
plugins: [
'#babel/plugin-proposal-object-rest-spread',
'#babel/plugin-transform-runtime',
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-proposal-class-properties',
],
runtimeHelpers: true,
}),
commonjs({ sourceMap: false }),

Related

How to avoid code duplication between entry points in Rollup?

I want to configure rollup to take a bunch of files on input & produce a bunch of files in dist that share some common code between them.
Here's the rollup config I use:
import path from 'path';
import pathsTransformer from 'ts-transform-paths';
import alias from '#rollup/plugin-alias';
import commonjs from '#rollup/plugin-commonjs';
import { nodeResolve } from '#rollup/plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
const plugins = [
peerDepsExternal(),
alias({
entries: [
{ find: '#', replacement: path.join(__dirname, '/src') },
{ find: '$root', replacement: __dirname },
],
}),
nodeResolve(),
typescript({
transformers: [() => pathsTransformer()],
}),
commonjs({
extensions: ['.js', '.ts'],
}),
];
export default [
{
input: './src/a.ts',
output: {
file: 'dist/a.js',
format: 'esm',
sourcemap: true,
},
plugins,
},
{
input: './src/b.ts',
output: {
file: 'dist/b.js',
format: 'esm',
sourcemap: true,
},
plugins,
},
];
The problem with this set-up is that if a.ts & b.ts both depend on some code, this common code gets bundled into each output file unnecessarily increasing the bundle size.
Since the output.format is esm (so, imports are available in the output), I'd rather expect rollup to split the shared code between the 2 files in a separate chunk & then make both files import that common code (which seems to be the thing rollup does by default anyway).
I assume the problem to be somewhere around nodeResolve or commonjs calls, but, I want my dependencies to be bundled. I just don't want to have them duplicated.
How do I optimize my output? Here's a reproduction to visualize it (dist included).
By returning an array of objects, you specify to rollup that you want to have independent bundles. If you just want to produce multiple files (which was exactly my case) you can just specify input as an object.
import path from 'path';
import pathsTransformer from 'ts-transform-paths';
import alias from '#rollup/plugin-alias';
import commonjs from '#rollup/plugin-commonjs';
import { nodeResolve } from '#rollup/plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
const plugins = [
peerDepsExternal(),
alias({
entries: [
{ find: '#', replacement: path.join(__dirname, '/src') },
{ find: '$root', replacement: __dirname },
],
}),
nodeResolve(),
typescript({
transformers: [() => pathsTransformer()],
}),
commonjs({
extensions: ['.js', '.ts'],
}),
];
export default {
input: {
a: './src/a.ts',
b: './src/b.ts',
},
output: {
dir: 'dist',
format: 'esm',
sourcemap: true,
},
plugins,
};

rollup-plugin-babel not recognizing JSX

I am having issues with RollupJS API. For some reason, its not recognizing JSX syntax. Rollup (API) is giving me this error.
Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
2: import App from "./App";
3: const MyApp = (props) => {
4: return <View index={<App />} url={props.url} serverOptions={props.serverOptions}/>;
^
5: };
6: export default MyApp;
Here's the Rollup config.
export const loadBuildConfigurationOptions = (tsconfigOptions: object, root: Path = Process.Cwd()): RollupOptions => {
return <RollupOptions> {
input: Path.FromSegments(root, "src", "index.ts").toString(),
output: [
{
dir: Path.FromSegments(root, "dist").toString(),
format: "cjs"
}
],
external: [
"solid-js",
"solid-js/web",
"solidus"
],
plugins: [
typescript(tsconfigOptions),
nodeResolve({
preferBuiltins: true,
exportConditions: ["solid"],
extensions: [".js", ".jsx", ".ts", ".tsx"]
}),
nodePolyfill(),
babel({
babelHelpers: "bundled",
presets: [["solid", { generate: "ssr", hydratable: true }]],
}),
json(),
styles(),
copy({
targets: [
{ src: 'src/assets/**/*', dest: 'dist/src/assets' }
]
}),
],
preserveEntrySignatures: false,
treeshake: true,
};
}
I am using this in a build tool for a SolidJS SSR library I am developing. what is happening here is this function generates the appropriate Rollup configuration file to for the project being build by the user of this library. The returned Rollup configuration object is then being used by the Rollup JS API to build the actual application. Do I need to change the parameters in the babel plugin or something?
Okay. I figured it out. It’s quite silly really. I just had to make one change to the Babel plugin.
babel({
babelHelpers: "bundled",
presets: [["solid", { generate: "ssr", hydratable: true }]],
extensions: [“.js”, “.ts”, “.jsx”, “.tsx”],
}),
Adding the extensions option seemed to have fixed everything.

Rollup.js - Use rollup.config.js in JS API?

I have a working rollup.config.js file but need to run a separate packaging script after Rollup is done. My plan was to use Rollup's watchers via their JS API. However, I cannot get the JS API to work at all.
I am referencing this code from the Rollup site...
const loadConfigFile = require('rollup/dist/loadConfigFile');
const path = require('path');
const rollup = require('rollup');
loadConfigFile(path.resolve(__dirname, 'rollup.config.js'))
.then(async ({options, warnings}) => {
warnings.flush();
const bundle = await rollup.rollup(options);
await Promise.all(options.output.map(bundle.write));
rollup.watch(options);
})
but I keep getting an error Unknown input options: 0.......Error: You must supply options.input to rollup
My rollup.config.js is as follow...
import svelte from 'rollup-plugin-svelte';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from "rollup-plugin-terser";
import replace from '#rollup/plugin-replace';
import json from '#rollup/plugin-json';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js'
},
plugins: [
json(),
production && replace({
'eruda': ``,
exclude: 'node_modules/**',
delimiters: ['import * as eruda from \'', '\'']
}),
production && replace({
'eruda': ``,
exclude: 'node_modules/**',
delimiters: ['', '.init()']
}),
svelte({
dev: !production,
css: css => {
css.write('public/bundle.css');
}
}),
resolve({ browser: true }),
commonjs(),
!production && livereload('public'),
production && terser()
],
watch: {
clearScreen: false
}
};
Any thoughts are appreciated!
I think the example at rollupjs.org is wrong. Shouldn't it be like this instead?
const loadConfigFile = require('rollup/dist/loadConfigFile')
const path = require('path')
const rollup = require('rollup')
// load the config file next to the current script;
// the provided config object has the same effect as passing "--format es"
// on the command line and will override the format of all outputs
loadConfigFile(path.resolve(__dirname, 'rollup.config.js'), {format: 'es'})
.then(({options, warnings}) => {
// "warnings" wraps the default `onwarn` handler passed by the CLI.
// This prints all warnings up to this point:
console.log(`We currently have ${warnings.count} warnings`)
// This prints all deferred warnings
warnings.flush()
// options is an "inputOptions" object with an additional "output"
// property that contains an array of "outputOptions".
// The following will generate all outputs and write them to disk the same
// way the CLI does it:
options.map(async options => {
const bundle = await rollup.rollup(options)
await Promise.all(options.output.map(bundle.write))
// You can also pass this directly to "rollup.watch"
rollup.watch(options)
})
})
Figured it out, apparently the options returned from loadConfigFile is an array so I had to do options[0] inside the async function

How to properly export an ES6 module function as a library for use in a node app?

Let's say that I have a node.js application, which does NOT go through my webpack bundling:
Node App
const Html = require('./build/ssr-bundle.js');
let result = Html.ssrbundle.render();
console.log(result);
Here is my ES6/JSX file, which is getting processed by webpack and I want to be able to access that render function in my node app (you guessed right, I am trying to SSR react stuff ;) )
src/Html.js -(webpack)-> build/ssr-bundle.js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import CustomComponent from './custom-component.js';
module.exports = {
render : function () {
return ReactDOMServer.renderToString(<CustomComponent />);
} };
And here is my Webpack config
webpack.config.js
var path = require('path');
module.exports = {
entry: {
ssr: './src/Html.js',
//frontend: './src/frontend-Html.js'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'ssr-bundle.js',
library: 'ssrbundle'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['env','react'],
plugins: ["transform-es2015-destructuring", "transform-object-rest-spread"]
}
},
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
},
stats: {
colors: true
},
devtool: 'source-map'
};
Whatever I do, I cannot figure out how to properly use that exported variable "ssrbundle" and subsequently the render function. If I had my node app included in the bundle, everything would be all right, but this is not what I want to do.
As apokryfos suggested, I played around with the libraryTarget Webpack setting. You can find more info on using Webpack to author a library (what I was really trying to achieve) here:
https://webpack.js.org/guides/author-libraries/
and here are code examples:
https://github.com/kalcifer/webpack-library-example/blob/master/webpack.config.babel.js.
What did the trick for me, was to set the libraryTarget to "umd" , which is different than the "var" setting which is set by default and is suitable i.e. for including the script in an HTML file

(!) Unused external imports. 'reduce' imported from external module 'lodash' but never used

I am using rollup to build my library and I have a dependency on lodash.
but when I run rollup to bundle my code, I get this warning.
(!) Unused external imports
reduce imported from external module 'lodash' but never used
A sample of my code is as follow:
import { reduce } from "lodash"
export function someutilityfunction(args) {
return reduce(args,() => {
// do somthing
}, {}) // A generic use case of reduce function
}
the bundled library works fine.
I have even tried using
import * as _ from "lodash"
and lodash-es instead of lodash
but no success.
Here is my rollup.config.js
import resolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import filesize from 'rollup-plugin-filesize'
import typescript from 'rollup-plugin-typescript2'
import commonjs from 'rollup-plugin-commonjs'
import uglify from 'rollup-plugin-uglify'
let production = (process.env.NODE_ENV == "production")
export default {
input: 'src/index.ts',
output: {
file: 'lib/index.js',
format: 'cjs',
name: 'my-library',
sourcemap: true
},
external: [
'rxjs',
'axios',
'lodash'
],
plugins: [
resolve(),
typescript({
tsconfigOverride: {
compilerOptions: {
declaration: true,
moduleResolution: "node",
allowSyntheticDefaultImports: true
}
},
// verbosity: 3,
clean: true,
rollupCommonJSResolveHack: true,
abortOnError: false,
typescript: require('typescript'),
}),
commonjs(),
babel({
exclude: 'node_modules/**'
}),
production && uglify(),
filesize()
],
watch: {
include: 'src/**'
}
};
I have used this rollup config before and it has worked fine, until now.
Am I missing something?
And I know the title of the question can be more generic. feel free to improve the post.
Looks like there is a known issue with the tree-shaking within Rollup and Lodash (also D3 has a similar problem):
https://github.com/rollup/rollup/issues/691

Categories