How to determine why something is imported in a Rollup chunk? - javascript

The content of the question is that I am analyzing why does my bundle include a lot of unused code.
In the screenshot, I see that there is a chunk being loaded called chunk-index.es.fbfa066e.js. 76% of that code is unused after loading the app.
If I look at the contents of this module, it basically bundles 3 other packages.
// #preserve module #/node_modules/.pnpm/is-plain-object#5.0.0/node_modules/is-plain-object/dist/is-plain-object.mjs
// #preserve module #/node_modules/.pnpm/immer#9.0.16/node_modules/immer/dist/immer.esm.mjs
// #preserve module #/node_modules/.pnpm/slate#0.87.0/node_modules/slate/dist/index.es.js
ane exports them as
export {
Element as E,
Node as N,
Operation as O,
Path as P,
Range as R,
Scrubber as S,
Transforms as T,
Editor as a,
Text as b,
Point as c,
createEditor as d,
isPlainObject as i
};
Now if I look at the initiated projects.page.client.f6019eee.js
However, the only reference to this module inside of projects.page.client is this import:
import "../../../chunk-Table.31bce020.js";
import "../../../chunk-index.es.fbfa066e.js";
import "../../../chunk-Tabs.0cbe576c.js";
i.e. It does not even import any of the named exports.
Why is chunk-index.es.fbfa066e.js import included in projects.page.client.f6019eee.js? (and how to debug this?)
How does projects.page.client.f6019eee.js use it given that it does not import its exports?
How do I tell rollupjs to not import this chunk?

There are several things at play here:
imports magically appearing in your entry points is usually a—harmless—load performance optimization by Rollup. This will not increase the amount of dead code but "hoists" imports that are otherwise imported by some dependency of that chunk to be loaded earlier, see https://rollupjs.org/faqs/#why-do-additional-imports-turn-up-in-my-entry-chunks-when-code-splitting
If in the original sources, you are using a lot of reexport why what people call "barrel files", you may want to look into setting treeshake.moduleSideEffects to false, at least for the barrel file. Otherwise, all imports that Rollup "thinks" have side effect of the barrel file will end up as dependencies for all chunks that import that file. Alternatively, do not use such files but directly import from the file that declares the variable. But sometimes that is not possible e.g. for typing reasons.

Related

What is the best way to point to common dependency from JS modules bundled together?

(Code below is a simple example, real scenario is bigger)
I have two modules, mod1.js and mod2.js, which are bundled together (using esbuild). They share a common dependency, util.js.
Problem is: when code in mod2.js imports util.js (using same alias), there's a conflict with names.
util.js:
export class Util {
...
}
mod1.js:
import Util from "./util.js";
...
mod2.js:
/* this raises an error of variable already declared */
import Util from "./util.js";
...
If I change alias in mod2.js, error goes away, as expected. But changing aliases every time I import util.js is a bit clumsy, and makes me think there has to be another way.
Is there a better approach to point to a common dependency from multiple modules which are bundled together?
Thanks in advance!
With help from #Bergi's comment, I figured out that I was not using esbuild to bundle my files, but rather using Hugo to concatenate them, and passing this to esbuild.
This leads to mentioned error because there are multiple imports in the same file, which esbuild correctly doesn't recognize as valid. Instead, using esbuild to bundle my files gives me correct results.
I'm still using Hugo, but I have a single entry point which it consumes, that imports all my scripts. From example, I have another file, say master.js:
master.js:
import mod1 from "./mod1.js";
import mod2 from "./mod2.js";
And I then pass this master.js to Hugo, using its js.Build function, which internally uses esbuild. This way I can import util.js using same alias, because these imports are in separate files, using ES6 linking from esbuild.

Barrel files, webpack and jest

I have few questions about barrel files, webpack and jest. I've never really wondered how they work and now I'm struggling (with jest) to write tests on a bigger application that does not have ones yet.
I have barrel files in big folders (like components) and they look like this:
/components/index.js
export { default as ComponentA } from './ComponentA';
export { default as ComponentB } from './ComponentB';
export { default as ComponentC } from './ComponentC';
With a setup like this I can easily import those components like this:
import { ComponentA, Component C } from '/components';
instead of writting
import Component A from '/components/ComponentA';
import Component C from '/components/ComponentC';
My main question: In my webpack bundled files, will the ComponentB file be included just because I have it in the components/index.js (but I do not really use it)?
This came to my mind after I started writting tests with jest and it started to throw me errors about files I haven't writted tests yet for. I've tried to search why, and the only reason I can find is that I have imports from barrel files in bigger files (e.g. I import ComponentA from a barrel file when building a page - that I'm now trying to test).
Barrel imports should be supported, at least if you are using ts-jest transform.
Ref: https://github.com/kulshekhar/ts-jest/issues/1149
but I have encouraged similar problem with barrel type definition src/types/index.d.ts referenced usually as #/types all around in codebase
I ended up with configuring extra moduleNameMapper definition
moduleNameMapper: {
// FIXES Could not locate module #/types mapped as: .../src/types.
'^#/types$': '<rootDir>/src/types/index.d',
},
Hope this will help someone :)
Actually yes, all files imported in a barrel file are currently included in the import on Jest. That happens mainly because Jest uses CommonJS and does not do any tree-shaking (which would actually just make everything even slower in this use case).
When working on a big project, it seems to be quite common to have Jest test suites run increasingly slow when using barrel files and that is related to the fact that the entire dependency tree has to be resolved before running the tests.
The quick solution for that is to not use barrel files for imports within the package, either by avoiding them completely or by mocking them using jest.mock.
I personally recommend avoiding imports completely on your modules by using dependency injection instead. If you really want to go the jest.mock way, all you have to do is use it with a factory, like this (from the Jest docs):
jest.mock('../moduleName', () => {
return jest.fn(() => 42);
});
// This runs the function specified as second argument to `jest.mock`.
const moduleName = require('../moduleName');
moduleName(); // Will return '42';
Yes, this will magically replace the imported file before it is imported, avoiding the barrel file issue altogether.
Again: I don't like magic code and recommend you use dependency injection instead.
Jest
I've had the same issue: Jest was throwing errors related to files that I didn't use, only because they were exported in a barrel file which was used by the modules I was testing.
In my case the issue was with tests on Vuex stores which started to break because of modules using the same stores (probably due to circular dependencies).
prices.test.js
// the test imports a module
import prices from '#/store/modules/prices';
prices.js
// the module imports a module from a barrel file
import { isObject } from '#/common';
index.js
// this is the imported module
export { default as isObject } from './isObject';
// this is the other module that breaks my tests, even if I'm not importing it
export { default as getPageTitle } from './getPageTitle';
getPageTitle.js
// when the module uses the store, an error is thrown
import store from '#/store/store';
I think that in my case the issue was a circular dependency, anyway to answer your question: yes, Jest imports all the modules in the barrel files even if they are not imported directly.
In my case the solution was to move the modules that were using Vuex to a separate folder and to create a separate barrel file only for those files.
Webpack
In a different moment I figured out that Webpack is doing the same thing by default. I didn't notice it on a project where modules were small and weren't importing libraries, but on a separate project I had modules importing very large libraries and I noticed that Webpack wasn't doing tree-shaking and wasn't optimizing chunks as it was supposed to do.
I discovered that Webpack imports all the libraries in the barrel file, in a similar way as Jest does. The upside is that you can stop this behavior by disabling side effects on the specific barrel files.
webpack.config.js
{
module: {
rules: [
// other rules...
{
test: [/src\/common\/index.ts/i],
sideEffects: false,
}
]
}
}
You can see the difference using Webpack Bundle Analyzer: when side effects are not turned off, all imports from one specific folder will have the same size. On the other hand, when side effects are turned off you will see a different size for different imports.
Default behavior (all imports the same size)
Side effects turned off (the size depends on which modules you import)
When files in the barrel files import large libraries, the improvements can be even more noticeable (tree-shaking works, chunks are optimized).
You can find more details here: https://github.com/vercel/next.js/issues/12557 and here: Webpack doesn't split a huge vendor file when modules are exported and imported using index files.

Preserve order of modules in Webpack

import('./A');
import('./B');
import('./C');
export class Person {};
A, B and C are plain JS (es5) libraries which use global window object and depends on each other.
Looking at the output file, I see that Webpack (awesome-typescript-loader) changes the order of modules, and it causes issues.
How to include them in the output file in exact same order?
The right answer is probably to convert your files to typescript but this answer assumes you have time to completely convert your existing js files to ts. If for some reason you don't, they should still work pretty well as partially converted ts files you just might have to take some conversion shortcuts.
I would only recommend doing this with existing known good working js files and only if you can't take the time to convert them which is why I am assuming you are asking the question.
copy your javascript *.js file content into new typescript *.ts files.
resolve all the lint complaints (you can err on the side of flexibility for your JS code)
i.e. using
// #ts-ignore
and the "any" type only when necessary
export the things used by your other files in your new ts files
i.e.
export {varible}
import those things by the dependent files i.e.
import {varible} from "./variableFile"
this should resolve your import order issues.

Relative vs. non-relative module import for custom modules

Update[09/12/2017 16:58 EST]
Added reason why I hesitate to use the natively-supported non-relative import with my own modules to the bottom of this question.
Update[09/12/2017 12:58 EST]:
Per request, I made the file structure below reflect my actual use case. Which is a nested view module requesting a utility module somewhere up the directory tree.
Per request
In both TypeScript and ES6, one can import custom module by
// Relative
import { numberUtil } from './number_util';
// OR
// Non-relative
import { numberUtil } from 'number_util';
And according to TypeScript docs (link), one should:
... use
relative imports for your own modules that are guaranteed to maintain
their relative location at runtime.
... Use non-relative paths when importing any
of your external dependencies.
My problem here is that I have a project structure like this:
project/
|--utils
| |--number_util.ts
|--views
| |--article_page
| |-- editor_view
| |--editor_text_area.ts
And when I include utils/number_util inside my editor_text_area module, the import statement looks like:
import { numberUtil } from './../../../utils/number_util';
Which is long and not readable and, worst of all, difficult to maintain: whenever I need to move editor_text_area, I will have to update each these relative paths, when in the meantime I can just use the non-relative way of
import { numberUtil } from 'utils/number_util';
Does anyone have any suggestions on how best to do module imports to achieve the highest readability and maintainability?
But using the non-relative way poses a problem (other than that it is not recommended by official docs): what if I installed an npm module that has the same name with the module I'm importing? On that note, it is not as safe as the uglier alternative mentioned above.
Depending on your project tooling and structure, you have some options.
A) You could publish part of your dependencies as stand-alone modules, perhaps in a private registry. You can then install them with npm and require them like any other external dependency.
B) Many module systems support some sort of path mapping. The vue-js webpack template uses webpack's alias feature to set # to the source code root. TypeScript supports path mapping too. After you introduce that mapping you can use
import { numberUtil } from '#/utils/number_util';
This approach basically introduces a private namespace for your modules.
It is safe, in that you could only ever shadow an npm module with the name # which is an invalid name and therefore cannot exist.
For your example, you would have to have these entries in your compilerOptions:
"baseUrl": ".",
"paths": {
"#/*": ["*"]
}
Or if you only want to import modules from utils, you could change the mapping to "#/*": ["utils/*"] and import using '#/number_util'.
C) Another thing to consider is to improve your project structure. Depending on your actual project, it might make sense to apply the facade pattern at one point or another. Maybe inject the dependency into editor_text_area instead of letting it import it itself.
you can add "baseUrl": "./src", to you tsconfig.json,
then you can import * as utils from 'utils' to import ./src/utils/index.ts

How to use js modules from non-module files

I'm a beginner at using js modules.
I'm working on a fairly simple web application. It uses typescript and angular 2, which heavily relies on modules.
Most of my app ts files 'import' one or many js modules (usually mostly angular 2 modules).
As I understand, because my app ts files have a top level 'import', they are automatically considered a js module by typescript.
However, I want any of my app ts files to be accessible by any other of my app ts files, without having to 'import' each other. But because they are now modules themselves, ts requires me to do that...
Is it possible?
It seems crazy to me that for each of my app ts file, I should have to declare every other of my app ts files that are used in there (I like to have tiny files with a single class/interface). In addition, this relies on relative paths which breaks as soon as I restructure my folder structure.
Am I thinking about this the wrong way?
You must have a js file which is an entry point to your application right?.. So in that file just import all the modules which you want to access without importing and attach them to the window object. Since the window object is available globally, you can access your module from anywhere without importing the corresponding module. For example,
Consider this scenario:
You have a module in a file called module1.ts
The entry point of your application is a file called index.ts
And you have a module2 where you require something from module1
// module1.ts
function add(first: number, second: number): number {
return first + second
}
export {add}
in your index.ts
// index.ts
import {add} from '<path to module1>/module1';
window.add = add
Now in your module2
// module2.ts
window.add(1, 2)
Since the window object is available globally you can attach as many properties to it as you like.
As far as the type resolution is concerned you can declare a window module with the add function you require in a .d.ts file as follows:
declare module window {
add: (first: number, second: number) => number
}
Declaring dependencies (e.g modules) for each file is a double-edged sword.
The advantage is that there is no 'magic' - you know exactly where each function, variable, class etc. is coming from. This makes it much easier to know what libraries / frameworks are being used and where to look to troubleshoot issues. Compare it to opposite approach that Ruby on Rails uses with Ruby Gems, where nothing is declared and everything is auto-loaded. From personal experience I know it becomes an absolute pain to try to workout where some_random_method is coming from and also what methods / classes I have access to.
You're right that the disadvantage is that it can become quite verbose with multiple imports and moving relative files. Modern editors and IDEs like WebStorm and Visual Studio Code have tools to automatically update the relative paths when you move a file and also automatically add the imports when you reference code in another module.
One practical solution for multiple imports is to make your own 'group' import file. Say you have a whole bunch of utility functions that you use in all your files - you can import them all into a single file and then just reference that file everywhere else:
//File: helpers/string-helpers.ts
import {toUppercase} from "./uppercase-helper";
import {truncate} from "./truncate-helper";
export const toUppercase = toUppercase;
export const truncate = truncate;
Then in any other file:
import * as StringHelpers from "../path-to/helpers/string-helpers";
...
let shoutingMessage = StringHelpers.toUppercase(message);
The disadvantage of this is that it may break tree shaking, where tools such as webpack remove unused code.
Is it possible
Not in any easy way. The ts file is a module and uses e.g. module.exports (if commonjs) that will need to be shimmed out. And that is just the runtime story. The TypeScript story will be harder and one way would be to make a .d.ts file for the module stating the contents as global.
Like I said. Not worth doing. Modules are the way forward instead of making something hacky.
It's not crazy at all. You are definitively thinking in the wrong way.
Actually what you don't like it's a common feature in all modern programming languages and it makes the code and structure of the app a lot clearer and simple to understand.
Without imports and going to old school way looks very crazy to me :)
You can have only chaos with so many global variables.

Categories