Creating a tree shakable library with rollup - javascript

I am trying to create a component library wie rollup and Vue that can be tree shakable when others import it. My setup goes as follows:
Relevant excerpt from package.json
{
"name": "red-components-with-rollup",
"version": "1.0.0",
"sideEffects": false,
"main": "dist/lib.cjs.js",
"module": "dist/lib.esm.js",
"browser": "dist/lib.umd.js",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
},
"devDependencies": {
/* ... */
}
And this is my entire rollup.config.js
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import vue from "rollup-plugin-vue";
import pkg from "./package.json";
export default {
input: "lib/index.js",
output: [
{
file: pkg.browser,
format: "umd",
name: "red-components"
},
{ file: pkg.main, format: "cjs" },
{ file: pkg.module, format: "es" }
],
plugins: [resolve(), commonjs(), vue()]
};
I have a fairly simple project structure with an index.js file and 2 Vue components:
root
∟ lib
∟ index.js
∟ components
∟ Anchor.vue
∟ Button.vue
∟ package.json
∟ rollup.config.js
My index.js imports the Vue files and exports them:
export { default as Anchor } from "./components/Anchor.vue";
export { default as Button } from "./components/Button.vue";
export default undefined;
If I don't do export default undefined; somehow any app importing my library cannot find any exports. Weird.
Now when I create another app and I import red-components-with-rollup like so:
import { Anchor } from "red-components-with-rollup";
and I open the bundle from my app, I will also find the source code of the Button.vue in my bundle, it has not been eliminated as dead code.
What am I doing wrong?

What is the build result of the ES format? Is it a single file or multiples, similar to your sources?
Considering your Rollup options, I’m guessing it bundles everything into a single file, which is most probably the reason it isn’t able to tree-shake it.
To keep your ES build into multiple files, you should change:
{ file: pkg.module, format: "es" }
Into:
{
format: "es",
// Use a directory instead of a file as it will output multiple
dir: 'dist/esm'
// Keep a separate file for each module
preserveModules: true,
// Optionally strip useless path from source
preserveModulesRoot: 'lib',
}
You’ll need to update your package.json to point module to the new build file, something like "module": "dist/esm/index.js".

There are some interesting pitfalls with tree shaking that this article covers that you might be interested in.
Other than that - does your build tooling for your consumer app support pure es modules and have tree shaking capabilities? If so, then i would just make sure your exported files are not doing any 'side-effecty' things that might confuse rollup.
To be on the safe side i would offer direct imports to for each of your components as well as one main index.js that exports them. At least you're giving people who are paranoid of shipping unused code the option ie -
import { Anchor } from "red-components-with-rollup/Anchor";

Related

How to make an import shortcut/alias in create-react-app?

How to set import shortcuts/aliases in create-react-app?
From this:
import { Layout } from '../../Components/Layout'
to this:
import { Layout } from '#Components/Layout'
I have a webpack 4.42.0 version.
I don't have a webpack.config.js file in the root directory. I've tried to create one myself with this code inside:
const path = require('path')
module.exports = {
resolve: {
alias: {
'#': path.resolve(__dirname, 'src/'),
}
}
};
But it doesn't seem to work. I've seen the NODE_PATH=. variant in .env file. But I believe, it is deprecated - better not to use. And also, I have a posstcss.config.js file. Because I've installed the TailwindCss and I import the CSS library there. I've tried to paste the same code there, but it also didn't work.
It is finally possible with Create React App v.3
Just put:
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
into jsconfig.json or tsconfig.json if you use Typescript
Here is wonderful article about this.
Simplest way to archive this follow below steps. (same way as #DennisVash showed as but in simple form)
Installation - install and setup CRACO.
yarn add #craco/craco
# OR
npm install #craco/craco --save
Create a craco.config.js file in the root directory and configure CRACO:
/* craco.config.js */
const path = require(`path`);
module.exports = {
webpack: {
alias: {
'#': path.resolve(__dirname, 'src/'),
'#Components': path.resolve(__dirname, 'src/components'),
'#So_on': path.resolve(__dirname, 'src/so_on'),
}
},
};
Update the existing calls to react-scripts in the scripts section of your package.json file to use the craco CLI:
/* package.json */
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
Done! Setup is completed.
Now let's test it.
// Before
import Button from "./form/Button"
import { Layout } from '../../Components/Layout'
// After
import Button from "#/form/Button"
import { Layout } from '#Components/Layout'
Documentation Craco
Thank you. :)
// Absolute path: paths which are relative to a specific path
import Input from 'components' // src/components
import UsersUtils from 'page/users/utils' // src/page/users/utils
// Alias path: other naming to specific path
import Input from '#components' // src/components
import UsersUtils from '#userUtils' // src/page/users/utils
In order for webpack's aliases to work, you need to configure the default webpack.config.js of create-react-app.
The official way is to use the eject script.
But the recommended way is to use a library without ejecting (find the most modern library for that).
VSCode IntelliSense
In addition, you should add jsconfig.json file for path IntelliSense in VSCode (or tsconfig.json), see followup question.
Now such code with IntelliSense will work:
// NOTE THAT THOSE ARE ALIASES, NOT ABSOLUTE PATHS
// AutoComplete and redirection works
import {ColorBox} from '#atoms';
import {RECOIL_STATE} from '#state';
If you want to use:
// this:
import MyUtilFn from 'utils/MyUtilFn';
// Instead of this:
import MyUtilFn from '../../../../utils/MyUtilFn';
use the node module plugin for resolving the urls https://www.npmjs.com/package/babel-plugin-module-resolver. By installing it and adding it to your webpack/babel.rc file.
Step 1
yarn add --dev babel-plugin-module-resolver
add this plugin
Step 2
in babel.config.js file
ALIAS NAME ALIAS PATH
#navigation ./src/navigation
#components ./src/components
#assets ./assets
[
"module-resolver",
{
root: ["./src"],
alias: {
"^~(.+)": "./src/\\1",
},
extensions: [
".ios.js",
".android.js",
".js",
".jsx",
".json",
".tsx",
".ts",
".native.js",
],
},
];
Step 3
import example
import SomeComponent from '#components/SomeComponent.js';
Step 4
restart server
yarn start
Reference link: How to use import aliases with React native and VSCode

what is the correct config for custom nuxt.js components directory?

<h1>Hello people</h1>
I am learning to use nuxt.js and my question is about the use of custom directories different from the default structure of an application made with nuxt.js
The issue: index.vue can't load .vue components from my components custom dir
in the nuxt.config.file i have this conf:
/*
**Custom directory structure configuration paths
**Change as desires
**Important Note: This is a function not well documented on nuxt.org
*/
dir: {
views: 'views',
components: 'views/components',
layouts: 'views/layouts',
pages: 'views/pages',
controllers: 'controllers/',
middleware: 'controllers/middleware',
models: 'models',
data: 'models/data',
files: 'models/files',
store: 'models/store',
sources: 'sources',
assets: 'sources/assets',
static: 'sources/static'
},
and in the index.vue default file from nuxt create project comand have this at the end of file
<script>
import Logo from '~components/Logo.vue'
import VuetifyLogo from '~components/VuetifyLogo.vue'
export default {
components: {
Logo,
VuetifyLogo
}
}
</script>
as and extra data the content by default of the jsconfig.json file is
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"#/*": ["./*"],
"~~/*": ["./*"],
"##/*": ["./*"]
}
},
"exclude": ["node_modules", ".nuxt", "dist"]
}
The error message in the shell or comand prompt is
ERROR Failed to compile with 2 errors friendly-errors 12:12:09
These dependencies were not found: friendly-errors 12:12:09
friendly-errors 12:12:09
* ~/components/Logo.vue in
./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib
??vue-loader-options!./views/pages/index.vue?vue&type=script&lang=js&
* ~/components/VuetifyLogo.vue in
./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib
??vue-loader-options!./views/pages/index.vue?vue&type=script&lang=js&
friendly-errors 12:12:09
To install them, you can run: npm install --save ~/components/Logo.vue ~/components/VuetifyLogo.vue
The error message at localhost/3000 is
Module not found: Error: Can't resolve
'~/components/Logo.vue'
in '/home/user/desktop/nuxt/views/pages'
Module not found: Error: Can't resolve
'~/components/VuetifyLogo.vue'
in '/home/user/desktop/nuxt/views/pages'
I do not know why is looking for a component on "views/pages/" if I already defined components at "views/components/" and "views" as "views", more info about what I do is explained at:
https://forum.vuejs.org/t/anyone-know-how-to-rename-move-a-nuxt-folder/44876/2
No errors when use the default dir structure.
!Happy Holidays
Taken from the Nuxt documentation: https://nuxtjs.org/docs/features/component-discovery/
If you have components in nested directories such as:
| components/
--| base/
----| foo/
------| Button.vue
The component name will be based on its own path directory and
filename. Therefore, the component will be:
BaseFooButton

Webpack is erroring when I attempt to import a directory containing modules

I'm trying to create a small npm library to make interfacing with an API a little neater. My folder structure is as follows...
dist/
index.js
src/
index.js
endpoints/
endpoint1.js
package.json
webpack.config.js
Within my src/index.js file I have..
import {endpoint1} from './endpoints'
module.exports = class lib {
...
}
When I npm run build (which runs webpack --display-error-details --mode production) webpack throws a big error saying "Module not found: Error: Can't resolve './endpoints' in 'my\project\dir\src'.
My webpack.config.js file currently looks like...
const path = require('path');
module.exports = {
mode: 'production',
entry: path.join(__dirname, '/src/index.js'),
output: {
path: path.resolve('dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /.js?$/,
exclude: /(node_modules)/,
use: 'babel-loader'
}
]
},
resolve: {
modules: [
path.resolve(__dirname, 'src/endpoints')
],
extensions: ['.js']
}
};
I can see similar questions have been asked before and the resolutions listed don't seem to work for me so I thought I'd post it incase im making a rookie error. If any more info is required just say! Sorry if it's fairly wall of texty. Thanks.
The correct import would be:
import endpoint1 from 'endpoint1';
By using resolve.modules you tell Webpack to look up non relative paths in that folder. The module name is "enpoint1".
But actually you should only do this with libraries that you use across your project, for an endpoint a relative import will be appropriate:
import endpoint1 from "./endpoints/endpoint1";
import {endpoint1} from './endpoints' means this:
import from file ./endpoints/index.js something that is exported under the name enpoint1 in that file. If you import directory then it refers to index.js under that directory, not to all other files. It doesn't exist in your setup.
Names inside {} refer to named imports. This goes only for es6 modules-style imports like import {...} from. If you ommit {} then you import the default. CommonJs-style imports like const {...} = require('') work differently. CommonJs does not have named imports and exports. It just will import default from that file and then fetch a field via object destructuring.
What you export is something unnamed(i.e. default) from file ./endpoints/enpoint1.js
Something is unnamed because you use module.exports = which is CommonJS-style export. CommonJS does not support named exports. This is equevalent to export default class lib ... in es6 modules-style exports.
IF you want to import many files under directory you can consider these solutions:
1) Often single import points are created. You make a index.js file. In it you import manually every file under the directoy that you want to export. Then you export it under names. Like this:
import a from './a.js';
import b from './b.js';
import c from './c.js';
export { a, b, c };
Then it will work
2) In some rare cases in might make sence to use fs.readdir or fs.readdirSync to scan the entire directory and dynamicly require files in a loop. Use it only if you must. E.g. db migrations.

How Should VSCode Be Configured To Support A Lerna Monorepo?

I have a lerna monorepo containing lots of packages.
I'm trying to achieve the following:
Ensure that VSCode provides the correct import suggestions (based on package names, not on relative paths) from one package to another.
Ensure that I can 'Open Definition' of one of these imports and be taken to the src of that file.
For 1. I mean that if I am navigating code within package-a and I start to type a function exported by package-b, I get a suggestion that will trigger the adding of an import: `import { example } from 'package-b'.
For 2. I mean that if I alt/click on the name of a function exported by 'package-b' while navigating the file from a different package that has imported it, I am taken to '/packages/namespace/package/b/src/file-that-contains-function.js',
My (lerna) monorepo is structured as standard, for example here is a 'components' package that is published as #namespace/components.
- packages
- components
- package.json
- node_modules
- src
- index.js
- components
- Button
- index.js
- Button.js
- es
- index.js
- components
- Button
- index.js
- Button.js
Note that each component is represented by a directory so that it can contain other components if necessary. In this example, packages/components/index exports Button as a named export. Files are transpiled to the package's /es/ directory.
By default, VSCode provides autosuggestions for imports, but it is confused by this structure and, for if a different package in the monorepo needs to use Button for example, will autosuggest all of the following import paths:
packages/components/src/index.js
packages/components/src/Button/index.js
packages/components/src/Button/Button.js
packages/components/es/index.js
packages/components/es/Button/index.js
packages/components/es/Button/Button.js
However none of these are the appropriate, because they will be rendered as relative paths from the importing file to the imported file. In this case, the following import is the correct import:
import { Button } from '#namespace/components'
Adding excludes to the project's jsconfig.json has no effect on the suggested paths, and doesn't even remove the suggestions at /es/*:
{
"compilerOptions": {
"target": "es6",
},
"exclude": [
"**/dist/*",
"**/coverage/*",
"**/lib/*",
"**/public/*",
"**/es/*"
]
}
Explicitly adding paths using the "compilerOptions" also fails to set up the correct relationship between the files:
{
"compilerOptions": {
"target": "es6",
"baseUrl": ".",
"paths": {
"#namespace/components/*": [
"./packages/namespace-components/src/*.js"
]
}
},
}
At present Cmd/Clicking on an import from a different package fails to open anything (no definition is found).
How should I configure VSCode so that:
VSCode autosuggests imports from other packages in the monorepo using the namespaced package as the import value.
Using 'Open Definition' takes me to the src of that file.
As requested, I have a single babel config in the root:
const { extendBabelConfig } = require(`./packages/example/src`)
const config = extendBabelConfig({
// Allow local .babelrc.js files to be loaded first as overrides
babelrcRoots: [`packages/*`],
})
module.exports = config
Which extends:
const presets = [
[
`#babel/preset-env`,
{
loose: true,
modules: false,
useBuiltIns: `entry`,
shippedProposals: true,
targets: {
browsers: [`>0.25%`, `not dead`],
},
},
],
[
`#babel/preset-react`,
{
useBuiltIns: true,
modules: false,
pragma: `React.createElement`,
},
],
]
const plugins = [
`#babel/plugin-transform-object-assign`,
[
`babel-plugin-styled-components`,
{
displayName: true,
},
],
[
`#babel/plugin-proposal-class-properties`,
{
loose: true,
},
],
`#babel/plugin-syntax-dynamic-import`,
[
`#babel/plugin-transform-runtime`,
{
helpers: true,
regenerator: true,
},
],
]
// By default we build without transpiling modules so that Webpack can perform
// tree shaking. However Jest cannot handle ES6 imports becuase it runs on
// babel, so we need to transpile imports when running with jest.
if (process.env.UNDER_TEST === `1`) {
// eslint-disable-next-line no-console
console.log(`Running under test, so transpiling imports`)
plugins.push(`#babel/plugin-transform-modules-commonjs`)
}
const config = {
presets,
plugins,
}
module.exports = config
In your case, I would make use of lerna in combination with yarn workspaces.
When running yarn install, all your packages are linked under your #namespace in a global node_modules folder. With that, you get IntelliSense.
I've set up an example repository here: https://github.com/flolude/stackoverflow-lerna-monorepo-vscode-intellisense
You just need to add "useWorkspaces": "true" to your lerna.json
lerna.json
{
"packages": ["packages/*"],
"version": "0.0.0",
"useWorkspaces": "true"
}
And the rest is just propper naming:
global package.json
{
"name": "namespace",
// ...
}
package.json of your component package
{
"name": "#namespace/components",
"main": "src/index.js",
// ...
}
package.json of the package that imports the components
{
"name": "#namespace/components",
"main": "src/index.js",
"dependencies": {
"#namespace/components":"0.0.0"
}
// ...
}
Then you can do the following:
import { Component1 } from '#namespace/components';
// your logic
Automatically Importing from #namespace
Unfortunately, I couldn't find a way to make this work in VSCode with a Javascript Monorepo. But there are some things you can do:
Use Typescript (tutorial, other tutorial)
Use module-alias
Add import {} from '#namespace/components' to the top of your file
Use Auto Import Extension
Edit: This is broken with the latest version of VSCode.
I finally managed to get this working reliably. You need to create a separate jsconfig.js for every package in your monorepo, for example:
{monorepo root}/packages/some-package/jsconfig.json:
{
"compilerOptions": {
"target": "es6",
"jsx": "preserve",
"module": "commonjs"
},
"include": ["src/**/*.js"],
"exclude": ["src/index.js"]
}
Note that I've excluded the src/index.js file so it doesn't get offered as an import suggestion from within that package.
This setup appears to achieve:
Intellisense import suggestions from packages instead of using relative paths.
Go to definition to source of other packages in the monorepo.
VSCode has been pretty flaky of late, but it seems to be working.
Note this is working for a JavaScript-only monorepo (not Typescript).

Ava doesn't know to import .jsx dependencies from source files used in tests

I have the following ava config in my package.json:
{
"ava": {
"require": [
"./tests/helpers/setup-browser-env.js",
"./tests/helpers/module-stubber.js",
"babel-register"
],
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
},
"failFast": true,
"verbose": false,
"no-cache": false,
"concurrency": 5
}
}
and I have the following project structure:
src
index.jsx
components
input.jsx
tests
index.test.js
index.jsx
import Input from './components/input';
console.log(Input);
input.jsx
import react from 'react';
export default () => <input></input>;
input.test.js
import app from '../src';
// ...
The problem is that ava is able to transpile my src/index.jsx file but it doesn't transpile input.jsx and I am seing an empty object logged in my console. It only works if I import the component using the .jsx extension like so: import Input from './component/input.jsx. I can't make it to work without specifying the extension.
Is this possible in AVA? In Webpack it is a simple config which handles these kinds of patterns.
Looking in the ava docs under the configuration section: https://github.com/avajs/ava#configuration I can't see any field which can help me. (also the config fields don't seem to be really detailed).
Any ideas?
Thanks in advance.
babel-register is in charge of requiring input.jsx, not AVA. It's surprising that importing from ../src works, but importing input does not. I don't really have an answer for you though.

Categories

Resources