-
Notifications
You must be signed in to change notification settings - Fork 508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Rollup's preserveModules #276
Comments
i like this. |
Don't forget that material-ui has package.json with module field in each directory. preserveModules is not enough for deep imports. |
Even if you're not directly using deep imports, preserving the original module's file structure can help bundlers to shake out entire files when they're unused (see webpack/webpack#9337 for a full discussion). The tl;dr; seems to be that, when we concatenate a library into a single file, Webpack (and others) are often unable to determine when it's safe to shake an external library out of the concatenated bundle. In this scenario, it's also worth noting that you don't need to publish from the build directory. Webpack seems like it's "smart enough" to support tree-shaking from libraries similar to MUI when using shallow imports. I'm wondering if concatenation even makes sense as a default for library authors today. In a world where most folks are using NPM and a bundler, it seems like single-file bundles should be the exception rather than the rule. |
@schmod Thanks for the informative link; supporting tree shaking better is an additional reason to avoid bundling the library. Publishing from the build directory would still be desirable, though, because it improves the aesthetics of doing direct deep imports instead of importing re-exported modules, and deep imports help speed up the compilation and parsing when developing, so it's not just about tree shaking for the end users. This issue is blocking me from using tsdx. |
@slikts so what's your solution or workaround at this time? We faced similar problem :( |
Based on a Twitter thread from @mweststrate today, it sounds as if this option may be key to getting proper tree-shaking working: |
Would just like to add that this is what is blocking us from using tsdx at work, proper tree-shaking is a very critical feature libraries should make use of! Hope it will be ready soon. 😄 |
@beeequeue you can already configure this in
You'll have to change your Also I'm not sure that the "proper tree-shaking" part is accurate, there's a response to that tweet from Evan You (Vue's creator and lead):
|
I have compared the result bundles of our applications (webpack+terser) with our current configurations ( This currently results in 19kb more after gzipping, which would be a lot more if we kept migrating more components - probably eventually reaching 1MB more I'll try that solution though, hopefully we can start using tsdx over what we have now, it's a real mess. 😄 |
I also don't think the use-case of "deep imports" will completely make sense for this feature, as, per another reply in that thread from Rollup's current maintainer (which itself is a reply to my own reply there), for CJS,
The multi-entry feature that I have pending support for in #367 is more aligned for the deep imports use-case and recommended later in that thread too. (and the remaining question in that PR is out-of-the-box support for something like "publishing from the build directory". the proposal in #437 is an alternative solution for that as well) Also, per my response in #382, |
We don't need this for "deep imports" - it's useful exclusively for the better tree-shaking it provides out of the box. If we do something like |
So I've experimented using Immer's config: Immer Configmodule.exports = {
rollup: (config, options) =>
options.format !== "esm"
? config
: {
...config,
// this makes sure sideEffects: true can clean up files
preserveModules: true,
output: {
dir: "dist",
},
},
} and it does output the correct file structure, but it requires some changes for everything else to still work correctly. The main problem is that our entry files are named So for this to work you will also need to rename the entry files to something other than I ended up writing a common config file that also checks that the Re-usable tree-shaking config// tsdx-configs.js
const { resolve } = require("path")
const { equal, notEqual } = require("assert")
const assertPkgPropertyEquals = (pkg, prop, expected) =>
equal(pkg[prop], expected, `\`pkgJson.${prop}\` should be \`${expected}\``)
const assertPackageJsonIsCorrect = (dirname) => {
const pkg = require(resolve(dirname, "package.json"))
notEqual(
pkg.source,
null,
"`pkgJson.source` field is missing, which is used as the entry point."
)
const [, filename] = /([\w-]+)\.\w+$/.exec(pkg.source) || []
notEqual(
filename,
"index",
"The `pkgJson.source` file cannot be named `index.*` due to tsdx output configuration."
)
assertPkgPropertyEquals(pkg, "main", "dist/index.js")
assertPkgPropertyEquals(pkg, "module", `dist/${filename}.js`)
assertPkgPropertyEquals(pkg, "types", `dist/${filename}.d.ts`)
}
module.exports = {
treeShaking: (dirname) => ({
rollup: (config, options) => {
assertPackageJsonIsCorrect(dirname)
return options.format !== "esm"
? config
: {
...config,
// this makes sure sideEffects: true can clean up files
preserveModules: true,
output: {
dir: "dist",
},
}
},
}),
}
// packages/package/tsdx.config.js
const { treeShaking } = require("../../tsdx-configs")
module.exports = treeShaking(__dirname) |
I was responding to the subject of this issue. There is also #321 that doesn't have this specific use-case. Given that "deep imports" aren't what I'll mark this as v0.15 milestone as well since they're basically duplicates now.
Can you provide a reproduction for this? I believe this would be helpful to the broader community as well. Immer also no longer uses |
Ah, my bad I missed the
Ah, sorry, yes, this is one of the issues in the current version of #535 . There I just specified But I actually made a better version with For usage here, the config would look like: module.exports = {
rollup(config, opts) {
if (opts.format === 'esm') {
config = { ...config, preserveModules: true }
config.output = { ...config.output, dir: 'dist/', entryFileNames: '[name].esm.js' }
delete config.output.file
}
return config
} You'll have to change your Have updated my previous code sample to this as well. |
I created an example reproduction using your better config. Config-wise, it might be a better idea to name the ESM files |
I've ran into another problem: if we use async/await and babel transpiles it we will get an invalid output due to It ends up outputting the following structure:
This is a problem I encountered myself in the past and wasn't able to fix myself. :( I guess we have to change how the plugin transforms async somehow? EDIT: Seems like one option has to be set to false for it to work properly, |
I use the method described by a @agilgur5 for my ui-kit library but also with postcss plugin. My tsdx.config.js
But for some reason, the builded components in the '/dist' directory have incorrect imports, for example Button.esm.js has I understand that this may not be preserveModules issue, but maybe someone can give an advice |
Current Behavior
Currently a single bundle is output by rollup and module structure is not preserved.
Desired Behavior
There should be a way to do deep imports from the packaged library like so:
import FooModule from "mylib/FooModule"
.Suggested Solution
Rollup has a
preserveModules
option that includes the module structure in the built files.A caveat is that the library should be published from the build directory to avoid having to include the build directory in the import path (like
import FooModule from "mylib/dist/FooModule"
).Who does this impact? Who is this for?
Here's a simple benchmark comparing deep vs bare imports from a large library: https://github.com/slikts/deep-vs-bare-import-bench
The performance impact of having to parse unused code is non-negligible for larger libraries, and it's compounding. Tree shaking only mitigates this for the final consumer but not while developing. Larger component libraries like MUI specifically support deep imports to avoid this problem.
The text was updated successfully, but these errors were encountered: