Skip to content

feat: remove unused import(...) if importee doesn't have side-effects#3911

Merged
IWANABETHATGUY merged 1 commit into
mainfrom
03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects
Mar 20, 2025
Merged

feat: remove unused import(...) if importee doesn't have side-effects#3911
IWANABETHATGUY merged 1 commit into
mainfrom
03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects

Conversation

@IWANABETHATGUY

@IWANABETHATGUY IWANABETHATGUY commented Mar 20, 2025

Copy link
Copy Markdown
Member

Description

  1. Closed [Feature Request]: remove unused import(...) if importee doesn't have side-effects #2827, closed feat(rust): remove unused import(..) if importee doesn't have side-effects #3598
  2. I need to add console.log or some module in tests will be broken since the dynamic module is side effects free.

@IWANABETHATGUY
IWANABETHATGUY marked this pull request as ready for review March 20, 2025 10:51

IWANABETHATGUY commented Mar 20, 2025

Copy link
Copy Markdown
Member Author

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch 2 times, most recently from 0c6e806 to e1e4fe8 Compare March 20, 2025 10:59
@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

I just inline the Object.freeze(....) directly because I don't see any necessity to declare an extra variable. Please fix me if I omit something.

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch from e1e4fe8 to 1fee96f Compare March 20, 2025 11:04
Base automatically changed from 03-20-chore_bump_and_unpin_pnpm_version to main March 20, 2025 11:05
@IWANABETHATGUY
IWANABETHATGUY force-pushed the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch from 1fee96f to 29551b0 Compare March 20, 2025 11:06
@netlify

netlify Bot commented Mar 20, 2025

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit 9ad4404
🔍 Latest deploy log https://app.netlify.com/sites/rolldown-rs/deploys/67dc01e7b7a2650008de15d0

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch from 29551b0 to f25fb46 Compare March 20, 2025 11:07
@github-actions

github-actions Bot commented Mar 20, 2025

Copy link
Copy Markdown
Contributor

Benchmarks Rust

  • target: main(d883f08)
  • pr: 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects(9ad4404)
group                                                               pr                                     target
-----                                                               --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol                     1.00     84.4±9.75ms        ? ?/sec    1.14     96.2±6.63ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify              1.00   105.7±14.16ms        ? ?/sec    1.18   125.1±11.32ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify-sourcemap    1.05   116.3±14.82ms        ? ?/sec    1.00   110.9±10.51ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap           1.00     80.6±4.23ms        ? ?/sec    1.07     86.2±6.82ms        ? ?/sec
bundle/bundle@rome-ts                                               1.03    144.9±8.14ms        ? ?/sec    1.00   140.0±14.83ms        ? ?/sec
bundle/bundle@rome-ts-minify                                        1.00   244.6±33.60ms        ? ?/sec    1.10   269.7±27.46ms        ? ?/sec
bundle/bundle@rome-ts-minify-sourcemap                              1.00    236.8±4.87ms        ? ?/sec    1.01    238.4±2.96ms        ? ?/sec
bundle/bundle@rome-ts-sourcemap                                     1.00    135.8±4.29ms        ? ?/sec    1.02    139.0±3.03ms        ? ?/sec
bundle/bundle@threejs                                               1.09     46.4±4.32ms        ? ?/sec    1.00     42.5±1.44ms        ? ?/sec
bundle/bundle@threejs-minify                                        1.09    98.9±14.31ms        ? ?/sec    1.00     90.3±8.99ms        ? ?/sec
bundle/bundle@threejs-minify-sourcemap                              1.00    103.9±5.48ms        ? ?/sec    1.17   121.7±20.80ms        ? ?/sec
bundle/bundle@threejs-sourcemap                                     1.00     49.2±1.90ms        ? ?/sec    1.09     53.5±2.97ms        ? ?/sec
bundle/bundle@threejs10x                                            1.07   498.8±24.75ms        ? ?/sec    1.00   466.8±34.82ms        ? ?/sec
bundle/bundle@threejs10x-minify                                     1.00  1150.3±82.05ms        ? ?/sec    1.01  1161.5±81.45ms        ? ?/sec
bundle/bundle@threejs10x-minify-sourcemap                           1.00  1313.2±83.42ms        ? ?/sec    1.04  1368.6±97.43ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                                  1.00   530.0±33.60ms        ? ?/sec    1.07   566.7±29.14ms        ? ?/sec
remapping/remapping                                                 1.00     26.1±0.26ms        ? ?/sec    1.11     29.0±1.81ms        ? ?/sec
remapping/render-chunk-remapping                                    1.07     75.2±6.70ms        ? ?/sec    1.00     70.6±5.96ms        ? ?/sec
scan/scan@rome-ts                                                   1.00     97.3±3.09ms        ? ?/sec    1.14    110.6±6.34ms        ? ?/sec
scan/scan@threejs                                                   1.00     31.9±1.42ms        ? ?/sec    1.10     34.9±2.86ms        ? ?/sec
scan/scan@threejs10x                                                1.07   348.7±15.17ms        ? ?/sec    1.00   325.7±16.31ms        ? ?/sec

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch from f25fb46 to 9ad4404 Compare March 20, 2025 11:54
@IWANABETHATGUY
IWANABETHATGUY requested review from hyfdev and shulaoda and removed request for shulaoda March 20, 2025 11:59
@shulaoda

Copy link
Copy Markdown
Member

Please hold off on merging for now. I've encountered some special cases before, and I need to test them first.

@shulaoda shulaoda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the following scenario is not supported:

// entry.ts
async function test() {
  const a = await import('./hello')
  const b = await import('./hello')

  console.log(a === b)
}

test()

// hello.ts

rolldown:

// entry.js
async function test() {
	const a = await import("./hello-hoTXCQ30.js");
	const b = await import("./hello-hoTXCQ30.js");
	console.log(a === b);
}
test();

// hello-hoTXCQ30.js

rollup:

async function test() {
  const a = await Promise.resolve().then(function () { return hello; });

  const b = await Promise.resolve().then(function () { return hello; });

  console.log(a === b);
}

test();

var hello = /*#__PURE__*/Object.freeze({
  __proto__: null
});

@shulaoda shulaoda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following scenario is also not supported:

// entry.ts
import('./hello')

// hello.ts
function loadTS() {
  try {
    return import('typescript').then((m) => m.default)
  } catch (e) {
    console.error('typescript must be installed to use "native" functions')
    throw e
  }
}

rolldown:

// entry.js
import("./hello-DavaYWk6.js");

// hello-DavaYWk6.js

rollup:

Promise.resolve().then(function () { return hello; });

var hello = /*#__PURE__*/Object.freeze({
  __proto__: null
});

@shulaoda shulaoda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// entry.ts
import('./a')
import('./b')

// a.ts
import('./b')

// b.ts

rolldown:

// entry.js
import("./a-h86hG9tv.js");
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

// a-h86hG9tv.js
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

rollup:

// entry.js
import('./a-CHPDa8MY.js');
Promise.resolve().then(function () { return b; });

var b = /*#__PURE__*/Object.freeze({
	__proto__: null
});

export { b };

// a-CHPDa8MY.js
import('./main.js').then(function (n) { return n.b; });

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

Our analyzer only analyzes top level stmt, so it is not our goal to support these patterns.

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author
// entry.ts
import('./a')
import('./b')

// a.ts
import('./b')

// b.ts

rolldown:

// entry.js
import("./a-h86hG9tv.js");
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

// a-h86hG9tv.js
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

rollup:

// entry.js
import('./a-CHPDa8MY.js');
Promise.resolve().then(function () { return b; });

var b = /*#__PURE__*/Object.freeze({
	__proto__: null
});

export { b };

// a-CHPDa8MY.js
import('./main.js').then(function (n) { return n.b; });

What do you mean by this example? Can you give some specific code rather than pseudo code?

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

I just inline the Object.freeze(....) directly because I don't see any necessity to declare an extra variable. Please fix me if I omit something.

As I said I don't find it very useful to declare an extra variable,

@IWANABETHATGUY
IWANABETHATGUY added this pull request to the merge queue Mar 20, 2025
@IWANABETHATGUY

IWANABETHATGUY commented Mar 20, 2025

Copy link
Copy Markdown
Member Author

I will try to support these pattern in next pr.

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author
// entry.ts
import('./a')
import('./b')

// a.ts
import('./b')

// b.ts

rolldown:

// entry.js
import("./a-h86hG9tv.js");
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

// a-h86hG9tv.js
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

rollup:

// entry.js
import('./a-CHPDa8MY.js');
Promise.resolve().then(function () { return b; });

var b = /*#__PURE__*/Object.freeze({
	__proto__: null
});

export { b };

// a-CHPDa8MY.js
import('./main.js').then(function (n) { return n.b; });

What's inside b.ts ?

@shulaoda

Copy link
Copy Markdown
Member

What's inside b.ts ?

empty file

@shulaoda

Copy link
Copy Markdown
Member

What do you mean by this example? Can you give some specific code rather than pseudo code?

I'm wondering if there are users who store the dynamic import object as identifiers. Currently, our implementation results in different dynamic import objects for the same module in different places.

Merged via the queue into main with commit 0ac283b Mar 20, 2025
@IWANABETHATGUY
IWANABETHATGUY deleted the 03-20-feat_remove_unused_import_._if_importee_doesn_t_have_side-effects branch March 20, 2025 17:08
@shulaoda

shulaoda commented Mar 20, 2025

Copy link
Copy Markdown
Member

I'm just listing some cases I encountered during my implementation attempts, more scenarios may need to be considered.

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author
// entry.ts
import('./a')
import('./b')

// a.ts
import('./b')

// b.ts

rolldown:

// entry.js
import("./a-h86hG9tv.js");
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

// a-h86hG9tv.js
Promise.resolve().then(function() {
	return /* @__PURE__ */ Object.freeze({ __proto__: null });
});

rollup:

// entry.js
import('./a-CHPDa8MY.js');
Promise.resolve().then(function () { return b; });

var b = /*#__PURE__*/Object.freeze({
	__proto__: null
});

export { b };

// a-CHPDa8MY.js
import('./main.js').then(function (n) { return n.b; });

For this case, both of rollup and rolldown generate two chunks, rollup will try generate an extra export symbol, I am not sure if it is a good strategy to export symbol during bundling, or chunk splitting only care about user defined export symbol.

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

What do you mean by this example? Can you give some specific code rather than pseudo code?

I'm wondering if there are users who store the dynamic import object as identifiers. Currently, our implementation results in different dynamic import objects for the same module in different places.我在想是否有用户将 dynamic import 对象存储为标识符。目前,我们的实现导致在不同位置对同一模块的动态导入对象不同。

Can you give an example? If the import stmt is store as an variable which means it is used, current impl will not generate Object.freeze(..)

@IWANABETHATGUY

IWANABETHATGUY commented Mar 20, 2025

Copy link
Copy Markdown
Member Author

I'm just listing some cases I encountered during my implementation attempts, more scenarios may need to be considered.

Will try to align with rollup but it is hard to align with them in all scenario because our chunk splitting impl in different way.

@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

The following scenario is also not supported:

// entry.ts
import('./hello')

// hello.ts
function loadTS() {
  try {
    return import('typescript').then((m) => m.default)
  } catch (e) {
    console.error('typescript must be installed to use "native" functions')
    throw e
  }
}

rolldown:

// entry.js
import("./hello-DavaYWk6.js");

// hello-DavaYWk6.js

rollup:

Promise.resolve().then(function () { return hello; });

var hello = /*#__PURE__*/Object.freeze({
  __proto__: null
});

For this case it because typescript module has side-effects, so that hello.ts is considered has side effects, this is a limitation of our bundler's side effects propagation. We only care about module side effects propagation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: remove unused import(...) if importee doesn't have side-effects

3 participants