I have a typescript library consists of multiple folders. Each folder contains an index.ts file which exports some business logic. I am trying to bundle this with rollup to achieve this behavior on the call site:
import { Button, ButtonProps } from 'my-lib/button'
import { Input, Textarea } from 'my-lib/input'
import { Row, Column } from 'my-lib/grid'
This is the directory structure:
I have a main index.ts
under src/
which contains:
export * from './button';
export * from './input';
export * from './grid';
With this style, I can do:
import { Button, Input, InputProps, Row, Column } from 'my-lib'
But I don't want this. I want to access to each module by their namespaces. If I remove exports from the index.ts
file, all I can do is:
import { Button } from 'my-lib/dist/button'
which is something I didn't see before. Adding dist/
to the import statement means I am accessing the modules via a relative path. I want my-lib/Button
.
I am using rollup. I tried to use alias
plugin but didn't work. Below is my rollup config:
const customResolver = resolve({
extensions: ['ts'],
});
export default {
input: `src/index.ts`,
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true,
// plugins: [terser()],
},
{
file: pkg.module,
format: 'es',
sourcemap: true,
plugins: [terser()],
},
],
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
external: [],
watch: {
include: 'src/**',
},
plugins: [
// Allow json resolution
json(),
// Compile TypeScript files
typescript({ useTsconfigDeclarationDir: true }),
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
commonjs(),
// Allow node_modules resolution, so you can use 'external' to control
// which external modules to include in the bundle
// https://github.com/rollup/rollup-plugin-node-resolve#usage
resolve(),
// Resolve source maps to the original source
sourceMaps(),
alias({
entries: [
{ find: 'my-lib/button', replacement: './dist/button' },
{ find: 'my-lib/input', replacement: './dist/input' },
{ find: 'my-lib/grid', replacement: './dist/grid' },
],
customResolver,
}),
],
};
And this is the tsconfig
file:
{
"compilerOptions": {
"target": "es5",
"module": "ES6",
"lib": ["ES2017", "ES7", "ES6", "DOM"],
"declaration": true,
"declarationDir": "dist",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": false,
"moduleResolution": "node",
"resolveJsonModule": true,
"baseUrl": "./src",
"paths": {
"my-lib/button": ["./src/button"],
"my-lib/input": ["./src/input"],
"my-lib/grid": ["./src/grid"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts"],
"include": ["src/**/*.ts"]
}
I don't know how to achieve the same structure as lodash/xxx
or material-ui/yyy
with rollup.
People suggest aliases or named exports but I couldn't make it work.
The closest thing to my problem is below question:
Import from subfolder of npm package
I want to achieve the same thing but with typescript
and rollup
.
I think I am missing something, thanks.
This is possible, but requires some extra steps. A mentioned above, this is the approach taken by Material-UI.
The trick is to publish a curated dist
folder, rather the root folder of your repo.
To begin with, let's just be clear that it doesn't matter whether your library is built using CommonJS or ESM. This is about module resolution.
Let's assume the project is called my-package
.
Now most projects, after we have built src/
to dist/
will have
my-package
package.json
src/
index.js
dist/
index.js
and in package.json
"main": "dist/index.js"
or for esm
"module": "dist/index.js"
Most projects just add .npmignore
and publish the root of the project, so when installed the project ends up in node_modules
like so:
node_modules
my-package/
package.json
dist/
index.js
Once installed, consider this import:
import myProject from "my-project";
The module resolver will do this (simplifying greatly, as the full algorithm is irrelevant here):
node_modules
my-project
package.json
main
or module
Which will work because we have
node_modules
my-package/
package.json
dist/
index.js
import something from "my-project/something";
The resolution algorithm will work with
node_modules
my-project/
somthing.js
also with
node_modules
my-project/
something/
index.js
and with
node_modules
my-project/
something/
package.json
where in the latter case it will again look at main
or module
.
But we have:
node_modules
my-package/
package.json
dist/
index.js
The trick is, instead of publishing your project root with its dist
folder, to "frank" the dist
folder and publish the dist
folder using npm publish dist
instead.
Frank (as in frank a letter) means you need to create a package.json
in your dist
folder; add README.md
LICENSE
etc.
A fairly short example of how this is done can be found here.
So, given we had after build:
node_modules
my-package/
package.json
dist/
index.js
something.js
Once published we get
node_modules
my-project/
package.json
index.js
something.js
Where package.json
is the curated one.
First of all, the only difference between
import { Button } from 'my-lib/dist/button'
and
import { Button } from 'my-lib/button'
is just one more directory level.
Once said that, until you have "outDir": "dist",
in your tsconfig.json
file you need to add dist/
to your import
statements.
Indeed, both the libraries you taken as example are distributed with files in the root directory: lodash directly has js
files in the root, while material-ui has not outDir
option in its tsconfig.json
file (which means to write output files to root directory).
Hope this helps.
After numerous trials and errors, I was able to get this working by passing in a list of inputs, using the preserveModules and preserveModulesRoot options, and a simple postinstall script.
Here's my rollup.config.js
const options = {
input: [
'src/index.ts',
'src/api/index.ts',
'src/components/index.ts',
'src/contexts/index.ts',
'src/hooks/index.ts',
'src/lib/index.ts',
'src/types/index.ts',
'src/utils/index.ts',
'src/UI/index.ts',
],
output: [
{
format: 'cjs',
dir: 'dist',
exports: 'auto',
preserveModules: true,
preserveModulesRoot: 'src',
sourcemap: true,
},
],
plugins: [
// Preferably set as first plugin.
peerDepsExternal(),
typescript({
tsconfig: './tsconfig.rollup.json',
}),
postcss({
extract: false,
modules: true,
use: ['sass'],
}),
],
};
export default options;
scripts/postinstall.sh
#!/usr/bin/env bash
set -e;
# skip postinstall if npm install for development
# rollup.config.js is not included in dist
if [ -f "rollup.config.js" ]; then
echo "skipping your package's postinstall routine.";
exit 0;
fi
echo 'Copying files from dist folder into root project folder...'
cp -r dist/* ./ && rm -rf dist
echo 'Postinstall done!'
package.json
"scripts": {
"postinstall": "./scripts/postinstall.sh",
},
This will compile and output all files to dist folder. The postinstall script will copy all files from dist into the root project folder.
Note*: The postinstall script should be skipped when running npm install locally. This is done by checking if rollup.config.js exists or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With