-
Notifications
You must be signed in to change notification settings - Fork 154
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
fix: typebox imports not being tree shaken #909
fix: typebox imports not being tree shaken #909
Conversation
@marcesengel Hi, thanks for this and sorry for the delay here, have been very busy of late (I need to catch up on some of these issues!!). Side EffectsSo, first things first, I actually need to figure out if TypeBox is genuinely side effect free, I'm not sure it is as the internal // ---------------------------------------------------------------------
// module A
// ---------------------------------------------------------------------
import { TypeRegistry } from '@sinclair/typebox'
TypeRegistry.Set('Foo', ...) // adds an entry
// ---------------------------------------------------------------------
// module B
// ---------------------------------------------------------------------
import { TypeRegistry } from '@sinclair/typebox'
// The question is, can module B be sure Foo is registered? The answer is likely
// "it depends on the import resolution order", where the resolution order and
// registration can be viewed as a side-effect.
TypeRegistry.Has('Foo') // true | false ? So, for this PR to merge, it needs to be determined if the above fits the criteria of 'side-effect'. If it doesn't then we can go ahead with this PR, otherwise may need to defer. Package.JsonJust on the PR updates. TypeBox actually constructs the publishable function resolveMetadata() {
const packagePath = Path.join(process.cwd(), 'package.json')
const packageJson = JSON.parse(Fs.readFileSync(packagePath, 'utf-8'))
return {
name: packageJson.name,
version: packageJson.version,
description: packageJson.description,
keywords: packageJson.keywords,
author: packageJson.author,
license: packageJson.license,
repository: packageJson.repository,
// flagged by socket.dev if not present
scripts: { test: 'echo test' },
// disable auto bundle strategy: see https://github.com/esm-dev/esm.sh#bundling-strategy
'esm.sh': { 'bundle': false },
// side-effect hint for bundlers (WebPack)
'sideEffects': false, // added
types: "./build/cjs/index.d.ts",
main: "./build/cjs/index.js",
module: "./build/esm/index.mjs"
}
} Thanks again! |
Hi @sinclairzx81, thanks for the response! I'll move it, no worries 🙌 SummaryThe ExplanationI addressed this concern in the ticket, quoting from #907 (comment):
ExamplesImport Onlyimport { TypeRegistry } from '@sinclair/typebox'
// eof Would be tree-shaken with Import & Usageimport { TypeRegistry } from '@sinclair/typebox'
TypeRegistry.Set('Foo', ...) Would not be tree-shaken, because a function is invoked (which by default is considered to have side-effects, even when the module import itself is marked as free of side effects through Note: the consuming projects Import & Pure Usageimport { Type } from '@sinclair/typebox'
const MyType = /*#__PURE__*/ Type.Object({})
// eof Would be fully tree-shaken with Let me know if anything is unclear! |
Current build tooling will not purge unused typebox imports, as it can't be sure the import is pure (free of side effects). This can be solved by adding the `sideEffects: false` field to the `package.json`.
3014eaa
to
44b2edc
Compare
@marcesengel Hi! I'm so sorry for the extended delay on this PR (I've been extraordinarily busy of late) So, I guess the biggest concern here is if downstream tooling observes
I would assume that if a module was flagged as side effect free, then a bundler "could" technically interpret that as a sign it is fine to duplicate and split modules in certain bundling configuration. A duplicated module split between two bundles is fine in the instance that module wasn't holding onto any state (pure functions), but I'm not sure in the case of the TypeRegistry which is holding onto a shared map (and where duplication of that registry would lead to issues) If you can address the above specifically, I'm happy to merge this one in, but do want to check first. I would be curious to know that WebPack does in module splitting scenarios. Again, sorry for the delay on this. |
@marcesengel Heya, Hey, thanks again for the PR and the research. I'm going to close this one off for now as this PR has been lingering for a while and I'm still not sure about the behavior of bundlers nor the duplication behaviors when splitting. The linked esm.sh issue (above) was one such case of this, but do imagine a bundler could reasonably assume it was ok to duplicate registry modules across splits if the configuration suggested it was side effect free (but registries can be written to during module resolution, suggesting potential for side effects) Again, I may be misinterpreting the exact definition of "sideEffect" here, so happy to open this one up again following some more discussions around it. I certainly would like to ensure TypeBox supports module splitting (it's been restructured to do so), but only keen to provide additional configurations if those configurations can be proven to not trip bundlers under a variety of setups (mostly module splitting) Also, sorry again for the initial delay on this one. |
Current build tooling will not purge unused typebox imports, as it can't be sure the import is pure (free of side effects). This can be solved by adding the
sideEffects: false
field to thepackage.json
.For example importing
SOME_CONSTANT
fromwill currently result in output similar to
as the package import is not denoted to be free of side effects. With the proposed change, it would instead result in
allowing to completely exclude TypeBox from the bundle. For more information see https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free.