diff --git a/website/docs/en/guide/optimization/tree-shaking.mdx b/website/docs/en/guide/optimization/tree-shaking.mdx index 6f2843bf2f9..d51f36da66f 100644 --- a/website/docs/en/guide/optimization/tree-shaking.mdx +++ b/website/docs/en/guide/optimization/tree-shaking.mdx @@ -1,398 +1,121 @@ -import WebpackLicense from '@components/WebpackLicense'; +# Tree Shaking - +Rspack supports tree shaking, a terminology widely used within the JavaScript ecosystem defined as the removal of unused code, commonly referred to as "dead code." Dead code arises when certain exports from a module are not used and they lack side effects, allowing such pieces to be safely deleted to reduce the final output size. -# Tree shaking +Upon setting the `mode` to `production`, Rspack by default enables a series of optimizations related to tree shaking, including: -Rspack supports tree shaking, which is a term commonly used within a JavaScript context to describe the removal of dead code. +- [usedExports](/config/optimization#optimizationusedexports): Checks whether module exports are utilized, allowing the removal of unused exports. +- [sideEffects](/config/optimization#optimizationsideeffects): Assesses modules for side effects. Modules without side effects can be optimized further via re-exports. +- [providedExports](/config/optimization#optimizationprovidedExports): Analyzes all exports and their sources of re-exportation. +- [innerGraph](/config/optimization#optimizationsinnergraph): Tracks the transmission of variables, enhancing the accuracy of determining whether exports are indeed used. -It relies on the import and export statements to detect if code modules are exported and imported for use between JavaScript files. +Below are examples to illustrate how these configuration options function. -Tree shaking will be enable when you set `mode` into `production`. +:::info +Note that Rspack does not directly remove dead code but labels unused exports as potential "dead code." These labels can then be recognized and processed by subsequent compression tools. As such, if compression features are turned off, no actual code removal will be observable. For enhanced readability, pseudocode might be used to demonstrate the effects of code removal. +::: -## Basic tree shaking +Let's understand this mechanism better through an example, assuming `src/main.js` as the project's entry point: -```js title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'development', - entry: { - index: './src/index.js', - }, - output: {}, -}; +```js title='src/main.js' +import { foo } from './util.js'; -module.exports = config; +console.log(foo); +// `bar` is not used ``` -```js title=./src/index.js -import { cube } from './math.js'; - -function component() { - const element = document.createElement('pre'); - - element.innerHTML = ['Hello webpack!', '5 cubed is equal to ' + cube(5)].join( - '\n\n', - ); - - return element; -} - -document.body.appendChild(component()); +```js title='src/util.js' +export const foo = 1; +export const bar = 2; ``` -```js title=./src/math.js -export function square(x) { - return x * x; -} - -export function cube(x) { - return x * x * x; -} -``` +In this example, `bar` from `util.js` is unused. In `production` mode, Rspack defaults to enabling the [usedExports](/config/optimization#optimizationusedexports) optimization, detecting which exports are actively used. Unused exports, like `bar`, are safely removed. The final output would resemble: -Note that we did not import the `square` method from the **src/math.js** module. That function is what's known as "dead code", -meaning an unused export that should be dropped. Now let's build the project. -This will yield the following result: - -```js -// ...skipping some trivial code -var __webpack_modules__ = { - './src/index.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - var _mathJs = __webpack_require__('./src/math.js'); - function component() { - const element = document.createElement('pre'); - element.innerHTML = [ - 'Hello webpack!', - '5 cubed is equal to ' + (0, _mathJs.cube)(5), - ].join('\n\n'); - return element; - } - document.body.appendChild(component()); - }, - './src/math.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - function _export(target, all) { - for (var name in all) - Object.defineProperty(target, name, { - enumerable: true, - get: all[name], - }); - } - _export(exports, { - square: function () { - return square; - }, - cube: function () { - return cube; - }, - }); - function square(x) { - return x * x; - } - function cube(x) { - return x * x * x; - } - }, -}; -// ... -``` +```js title='dist/main.js' +const foo = 1; -As you can see, if we don't enable the tree shaking, all the code is kept as is, except wrapping with -some runtime code. - -Now, we switch to production mode and rebuild the project. In order to make the output more readable, we also turn off the `minimize` option and switch the `moduleIds` to `named`. To compare with the later chapters, we turn off `optimization.sideEffects`. - -```diff title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, -+ optimization: { -+ sideEffects: false, -+ moduleIds: 'named', -+ minimize: false -+ } -}; - -module.exports = config; +console.log(foo); ``` -Rebuild the project, the `square` function got dropped. +## Side Effects Analysis -## SideEffects +In `production` mode, Rspack also typically analyzes modules for the presence of side effects. If all exports from a module are unused and the module is devoid of side effects, then the entire module can be deleted. Let's modify the previous example a bit: -In a 100% ESM module world, identifying side effects is straightforward. However, we aren't there quite yet (In real project we using different kinds of format of package cjs, esm, umd and so on.), so in the mean time, it's necessary to provide hints to rspack's compiler on the "pureness" of your code. -The way this is accomplished is the "sideEffects" package.json property. +```diff title='src/main.js' +import { foo } from './util.js'; -```json title=package.json -{ - "name": "your-project", - "sideEffects": false -} +- console.log(foo); +// `bar` is not used ``` -The sideEffects field supports the following values: - -- `false` All files in this package have no side effects. -- `string` A glob matching files that includes side effects. -- `Array` An array of globs matching files that include side effects. -- `undefined` This is the default value of `sideEffects` when you don't provide any value to `sideEffects` field of `package.json`, When the `undefined` is set, Rspack will try to analyse if the code has sideEffects when `optimization.sideEffects` is `true` - (which is the default value when `mode` is `production`), or Rspack will treat all the modules in package has `sideEffect` - -This time, we use a more complicated demo. - -```js title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, - optimization: { - moduleIds: 'named', - minimize: false, - }, -}; -module.exports = config; -``` +In this case, none of the exports from `util.js` are used, and it’s analyzed as having no side effects, permitting the entire deletion of `util.js`. -```js title=index.js -import { multiply } from 'math'; +You may manually indicate whether a module retains side effects through `package.json` or `module.rules`. For information on how to do so, please consult [sideEffects](/config/optimization#optimizationsideeffects). -console.log(multiply(2, 3)); -``` +## Re-export Analysis -```json title=node_modules/math/package.json -{ - "name": "math", - "sideEffects": false -} -``` +Re-exports are common in development. However, a module might pull in numerous other modules while typically only a fraction of those are needed. Rspack optimizes this situation by ensuring that the referring party can access the actual exported modules directly. Consider this example involving re-exports: -```js title=node_modules/math/index.js -export * from './add.js'; -export * from './multiply.js'; -export * from './subtract.js'; +```js title='src/main.js' +import { value } from './re-exports.js'; +console.log(value); ``` -```js title=node_modules/math/subtract.js -export const subtract = (a, b) => a - b; +```js title='src/re-exports.js' +export * from './value.js'; +export * from './other.js'; // this can be removed if `other.js` does not have any side effects ``` -```js title=node_modules/math/multiply.js -export const multiply = (a, b) => a * b; +```js title='src/value.js' +export const value = 42; +export const foo = 42; // not used ``` -```js title=node_modules/math/add.js -const randomDate = Date.now(); -export const addRandomDate = a => a + randomDate; -export const add = (a, b) => a + b; -``` +Rspack defaults to enable [providedExports](/config/optimization#optimizationprovidedexports), which can analyze all exports from a re-exporting module and identify their respective origins. -The variable `randomDate` would still be needed because it includes a side effect that runs during the module's initialization. -However, because the package.json includes the `sideEffects` field and the value is `false`, and there are no export variable used in -**add.js**, so the whole module could be skipped, same for **subtract.js** - -```js -//... -var __webpack_modules__ = { - './node_modules/math/index.js': function ( - module, - exports, - __webpack_require__, - ) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - __webpack_require__.es( - __webpack_require__('./node_modules/math/multiply.js'), - exports, - ); - }, - './node_modules/math/multiply.js': function ( - module, - exports, - __webpack_require__, - ) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - Object.defineProperty(exports, 'multiply', { - enumerable: true, - get: function () { - return multiply; - }, - }); - const multiply = (a, b) => a * b; - }, - './src/index.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - var _indexJs = __webpack_require__('./node_modules/math/index.js'); - console.log((0, _indexJs.multiply)(2, 3)); - }, -}; -// ... -``` +If `src/re-exports.js` contains no side effects, Rspack can convert the import in `src/main.js` from `src/re-exports.js` directly into imports from `src/value.js`, effectively: -### module.rule.sideEffects - -You could override some modules sideEffects by using `module.rule.sideEffects`. -Why do we need such feature? -Considering if the author of package `math` forgot to add `sideEffects` field in package.json: - -```diff title=node_modules/math/package.json -{ -+ "name": "math" -- "name": "math", -- "sideEffects": false -} +```diff title='src/main.js' +- import { value } from './re-exports.js'; ++ import { value } from './value.js'; +console.log(value); ``` -Then Rspack will try to analyze the code safely and only mark the module is sideEffectFree when -all toplevel statements are sideEffectFree. As you can see, `math/index.js`, `math/subtract.js` and `math/multiply.js` is sideEffectFree -while `math/add.js` is not, because of `const randomDate = Date.now()`. When rebuilding the project, -you could see the diff: - -```diff -//... -var __webpack_modules__ = { -"./node_modules/math/index.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -__webpack_require__.es(__webpack_require__("./node_modules/math/multiply.js"), exports); -+__webpack_require__.es(__webpack_require__("./node_modules/math/add.js"), exports); -}, -"./node_modules/math/multiply.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "multiply", { - enumerable: true, - get: function() { - return multiply; - } -}); -const multiply = (a, b)=>a * b; -}, -+"./node_modules/math/add.js": function (module, exports, __webpack_require__) { -+ "use strict"; -+ Date.now() -+}, -"./src/index.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _indexJs = __webpack_require__("./node_modules/math/index.js"); -console.log((0, _indexJs.multiply)(2, 3)); -}, - -} -// ... -``` +This approach benefits by entirely ignoring the `src/re-exports.js` module. -That's not what we expect, then we could use `module.rule.sideEffects`, because it has higher priority than `sideEffects` in package.json. -Since the initialization side effect is only meaningful when addRandomDate is utilized, we can safely override it. To do so, we can make the following modification to our rspack.config.js: - -```diff title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, - optimization: { - minimize: false, - moduleIds: 'named' - }, -+ module: { -+ rules: [ -+ { -+ test: /math\/add\.js/, -+ sideEffects: false -+ } -+ ] -+ } -}; -module.exports = config; -``` +With an ability to analyze all re-exports in `src/re-exports.js`, it is determined that `foo` from `src/value.js` is not used and will be removed in the final output. -Rebuilding the project, we could got the same result as before, the whole **math/add.js** module got dropped. +## Variable Transmission -### Reexports optimization +In some cases, even though exports are accessed, they might not actually be used. For example: -After enabling SideEffects optimization, Rspack tries to optimize re-exports modules. +```js title='src/main.js' +import { foo } from './value.js'; -```js title=sdk.js -export { a } from './a.js'; -export { b } from './b.js'; -export { c } from './c.js'; -// ... -``` +function log() { + console.log(foo); +} // `log` is not used -```js title=index.js -import { a } from './sdk.js'; -console.log(a); +const bar = foo; // `foo` is not used ``` -If Rspack knows that module `sdk.js` has no side effects, Rspack will try to import `a` directly from `a.js` rather than imports the whole `sdk.js`. +In the scenario above, even though the `log` function and the variable `bar` depend on `foo`, since neither is used, `foo` can still be considered dead code and be deleted. -In some non-standard libraries, there may be circular dependencies, for example, `a.js` imports members from `b.js` via `sdk.js`. +After enabling [innerGraph](/config/optimization#optimizationinnergraph) optimization (enabled by default for `production` mode), for complex cross-module situations, Rspack maintains the ability to track variable usage, thereby achieving precise code optimization. -```js title=a.js -import { b } from './sdk.js'; -export const a = b; +```js title='src/main.js' +import { value } from './bar.js'; +console.log(value); ``` -```js title=index.js -import { a } from './sdk.js'; -console.log(a); +```js title='src/bar.js' +import { foo } from './foo.js'; +const bar = foo; +export const value = bar; ``` -At this time, there is a circular dependency between `a.js` and `sdk.js`, which is not recommended to intentionally use circular dependencies like this. However, it can still be built successfully and run successfully. This is because Rspack will attempt to transform the above imports into: - -```diff title=a.js -- import { b } from './sdk.js'; -+ import { b } from './b.js'; -export const a = b; +```js title='src/foo.js' +export const foo = 42; ``` -```diff title=index.js -- import { a } from './sdk.js' -+ import { a } from './a.js' -console.log(a); -``` - -After the transformation, the circular dependency is gone. However, if you try to turn off `optimization.providedExports` or `optimization.sideEffects`, the build will be successful, but errors due to circular dependencies will occur at runtime. - -> You can find above example [here](https://github.com/rspack-contrib/rspack-examples/tree/main/rspack/rspack/treeshaking-transform-imports). - -This diagram demonstrates the process of re-exports optimization: - -![](https://assets.rspack.dev/rspack/assets/rspack-reexports-optimization.png) +In this context, because `value` is eventually used, the `foo` it depends on is retained. diff --git a/website/docs/zh/guide/optimization/tree-shaking.mdx b/website/docs/zh/guide/optimization/tree-shaking.mdx index 0dbd1d287e8..e792fbccbab 100644 --- a/website/docs/zh/guide/optimization/tree-shaking.mdx +++ b/website/docs/zh/guide/optimization/tree-shaking.mdx @@ -2,392 +2,124 @@ import WebpackLicense from '@components/WebpackLicense'; -# Tree shaking +# Tree Shaking -Rspack 支持 tree shaking,这是在 JavaScript 上下文中常用的术语,用于描述死代码的删除。 +Rspack 支持 tree shaking 功能,这是一个在 JavaScript 生态中广泛使用的术语,主要用于去除未被访问的代码,俗称“死代码”。当一个模块的某些导出未被使用且不存在副作用时,这部分代码就可以被安全地删除,以减小最终产物的体积。 -它依赖于 import 和 export 语句来检测代码模块是否被导出和导入以在 JavaScript 文件之间使用。 +在设置 `mode` 为 `production` 后,Rspack 会默认启用一系列与 tree shaking 相关的优化措施。 -当你将 `mode` 设置为 `production` 时,Rspack 将默认启用 tree shaking。 +- [usedExports](/config/optimization#optimizationusedexports) 检查模块导出是否被使用到,没被使用到的导出可以被移除。 +- [sideEffects](/config/optimization#optimizationsideeffects) 检查模块是否包含副作用,如果不包含副作用可以被重导出优化。 +- [providedExports](/config/optimization#optimizationprovidedExports) 分析模块的所有导出和重导出来源。 +- [innerGraph](/config/optimization#optimizationsinnergraph) 追踪变量的传递,更加准确地判断导出是否真的被使用到。 -## Basic tree shaking +下面有一些例子来说明各个配置项的作用。 -```js title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'development', - entry: { - index: './src/index.js', - }, - output: {}, -}; +:::info +需要注意的是,Rspack 本身不直接删除死代码,而是标记未使用的导出为可能的“死代码”。这些代码能被后续的压缩工具识别并处理。因此,如果压缩功能被关闭,你将不会看到代码有任何实际的删除效果。为了增强文档的可读性,我们可能会使用伪代码来展示代码的删除效果。 +::: -module.exports = config; -``` - -```js title=./src/index.js -import { cube } from './math.js'; - -function component() { - const element = document.createElement('pre'); +让我们通过一个例子来更好地理解这一机制,假设 `src/main.js` 是项目的入口文件: - element.innerHTML = ['Hello webpack!', '5 cubed is equal to ' + cube(5)].join( - '\n\n', - ); +```js title='src/main.js' +import { foo } from './util.js'; - return element; -} - -document.body.appendChild(component()); +console.log(foo); +// `bar` is not used ``` -```js title=./src/math.js -export function square(x) { - return x * x; -} - -export function cube(x) { - return x * x * x; -} +```js title='src/util.js' +export const foo = 1; +export const bar = 2; ``` -注意到,我们没有从 **src/math.js** 模块导入 `square` 方法。该函数是“死代码”,表示应该被删除的未使用的导出。现在构建我们的项目 -将产生以下构建结果: - -```js -// ... 省略一些不重要的代码 -var __webpack_modules__ = { - './src/index.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - var _mathJs = __webpack_require__('./src/math.js'); - function component() { - const element = document.createElement('pre'); - element.innerHTML = [ - 'Hello webpack!', - '5 cubed is equal to ' + (0, _mathJs.cube)(5), - ].join('\n\n'); - return element; - } - document.body.appendChild(component()); - }, - './src/math.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - function _export(target, all) { - for (var name in all) - Object.defineProperty(target, name, { - enumerable: true, - get: all[name], - }); - } - _export(exports, { - square: function () { - return square; - }, - cube: function () { - return cube; - }, - }); - function square(x) { - return x * x; - } - function cube(x) { - return x * x * x; - } - }, -}; -// ... -``` +在上述示例中,`util.js` 中的 `bar` 没有被使用。在 `production` 模式下,Rspack 会默认启用 [usedExports](/config/optimization#optimizationusedexports) 优化,这能检测出哪些导出实际被使用了。未使用的导出如 `bar`,将被安全删除。最终的产物将类似: -正如你所看到的,如果我们不启用 tree shaking,所有代码都保持不变,只是将代码包裹了一层运行时代码。 - -现在,我们切换到 `production` 模式,重新构建项目,为了让产物更加可读,我们同时关闭 `minimize` 选项 以及切换 `moduleIds` 为 `named`,为了和后面章节对比,我们关闭 `optimization.sideEffects`。 - -```diff title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, -+ optimization: { -+ sideEffects: false, -+ moduleIds: 'named', -+ minimize: false -+ } -}; - -module.exports = config; -``` +```js title='dist/main.js' +const foo = 1; -重新构建项目后,`square` 函数将会被删除。 +console.log(foo); +``` -## SideEffects +## 副作用分析 -在一个 100% ESM 模块化的世界里,识别副作用是比较直接的。然而,我们还没有达到那个阶段 (在实际的生产代码中有各种格式的代码混用,cjs、esm 和 umd 等等),所以在此期间,需要在你的代码中提供“纯度”方面的提示给 Rspack 的编译器。 -该功能通常由 "sideEffects" package.json 属性来完成的。 +在 `production` 模式中,Rspack 也会默认分析模块是否含有副作用。如果一个模块的所有导出都未被使用且没有副作用,那么整个模块都可以被删除。我们对上面的例子做一些修改: -```json title=package.json -{ - "name": "your-project", - "sideEffects": false -} -``` +```diff title='src/main.js' +import { foo } from './util.js'; -sideEffects 字段支持以下值: - -- **false** 这个包中的所有文件都没有副作用。 -- **string** 匹配包含副作用文件的 glob。 -- **Array\** 匹配包含副作用文件的 glob 数组。 -- **undefined** 当你不设置 `package.json` 的 `sideEffects` 时的默认值。当 `optimization.sideEffects` 为 true 时,Rspack 将尝试分析代码是否具有副作用,当 `optimization.sideEffects` 为 'flag' 时,Rspack 将默认包中的所有模块均有副作用。 - -这次,我们使用一个更复杂的示例。 - -```js title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, - optimization: { - moduleIds: 'named', - minimize: false, - }, -}; -module.exports = config; +- console.log(foo); +// `bar` is not used ``` -```js title=index.js -import { multiply } from 'math'; +此时 `util.js` 文件中的导出都没有被使用,且可以分析出该文件没有副作用,所以 `util.js` 可以被整个删除。 -console.log(multiply(2, 3)); -``` - -```json title=node_modules/math/package.json -{ - "name": "math", - "sideEffects": false -} -``` +你也可以通过 `package.json` 或 `module.rules` 来手动标记模块是否包含副作用,如何标记请查看 [sideEffects](/config/optimization#optimizationsideeffects)。 -```js title=node_modules/math/index.js -export * from './add.js'; -export * from './multiply.js'; -export * from './subtract.js'; -``` +## 重导出分析 -```js title=node_modules/math/subtract.js -export const subtract = (a, b) => a - b; -``` +重导出在开发中非常常见,但如果一个模块中包含重导出过多,这个模块会引入太多的其他模块,但一般我们只需要其中的一小部分,Rspack 能够优化这种场景,确保引用方可以直接访问到实际的导出模块。看以下包含重导出的例子: -```js title=node_modules/math/multiply.js -export const multiply = (a, b) => a * b; +```js title='src/main.js' +import { value } from './re-exports.js'; +console.log(value); ``` -```js title=node_modules/math/add.js -const randomDate = Date.now(); -export const addRandomDate = a => a + randomDate; -export const add = (a, b) => a + b; +```js title='src/re-exports.js' +export * from './value.js'; +export * from './other.js'; // this can be removed if `other.js` does not have any side effects ``` -变量 `randomDate` 在默认情况下是需要被保留的,因为它在模块初始化时期包含副作用。 -但是,由于 package.json 中包含了 sideEffects 字段,且值为 false,除此之外在 add.js 中没有使用任何导出变量,因此整个模块都可以被跳过,subtract.js 同理。 - -```js -//... -var __webpack_modules__ = { - './node_modules/math/index.js': function ( - module, - exports, - __webpack_require__, - ) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - __webpack_require__.es( - __webpack_require__('./node_modules/math/multiply.js'), - exports, - ); - }, - './node_modules/math/multiply.js': function ( - module, - exports, - __webpack_require__, - ) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - Object.defineProperty(exports, 'multiply', { - enumerable: true, - get: function () { - return multiply; - }, - }); - const multiply = (a, b) => a * b; - }, - './src/index.js': function (module, exports, __webpack_require__) { - 'use strict'; - Object.defineProperty(exports, '__esModule', { - value: true, - }); - var _indexJs = __webpack_require__('./node_modules/math/index.js'); - console.log((0, _indexJs.multiply)(2, 3)); - }, -}; -// ... +```js title='src/value.js' +export const value = 42; +export const foo = 42; // not used ``` -### module.rule.sideEffects - -你可以使用 `module.rule.sideEffects` 覆盖某些模块的 sideEffects 选项。 +Rspack 默认开启 [providedExports](/config/optimization#optimizationprovidedexports),这可以分析出重导出模块中的所有导出以及他们各自的来源。 -为什么我们需要这样的功能呢?我们仍然使用上面的例子,假设 math 包的作者忘记在 package.json 中添加 sideEffects 选项: +如果 `src/re-exports.js` 不包含副作用,Rspack 可以将 `src/main.js` 中对 `src/re-exports.js` 的引入,直接转换成对 `src/value.js` 的引入,效果类似于: -```diff title=node_modules/math/package.json -{ -+ "name": "math" -- "name": "math", -- "sideEffects": false -} +```diff title='src/main.js' +- import { value } from './re-exports.js'; ++ import { value } from './value.js'; +console.log(value); ``` -Rspack 将尝试安全地分析代码,并仅在所有顶级语句均没有副作用时标记模块为无副作用。 - -正如我们所看到的,**math/index.js**、**math/subtract.js** 和 **math/multiply.js** 都没有副作用,而 math/add.js 不是,因为 `const randomDate = Date.now()` 含有副作用。当我们重新构建项目时,你可以看到差异如下: - -```diff -//... -var __webpack_modules__ = { -"./node_modules/math/index.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -__webpack_require__.es(__webpack_require__("./node_modules/math/multiply.js"), exports); -+__webpack_require__.es(__webpack_require__("./node_modules/math/add.js"), exports); -}, -"./node_modules/math/multiply.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -Object.defineProperty(exports, "multiply", { - enumerable: true, - get: function() { - return multiply; - } -}); -const multiply = (a, b)=>a * b; -}, -+"./node_modules/math/add.js": function (module, exports, __webpack_require__) { -+ "use strict"; -+ Date.now() -+}, -"./src/index.js": function (module, exports, __webpack_require__) { -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _indexJs = __webpack_require__("./node_modules/math/index.js"); -console.log((0, _indexJs.multiply)(2, 3)); -}, - -} -// ... -``` +这样做的好处是可以直接忽略掉整个 `src/re-exports.js` 模块。 -由于`module.rule.sideEffects` 比 package.json 中的 `sideEffects` 优先级更高,我们可以使用 `module.rule.sideEffects` 来覆盖某些模块的 `sideEffects` 标识. -由于初始化的副作用只在使用 addRandomDate 时才有意义,因此我们可以安全地覆盖它。为此,我们可以对 rspack.config.js 进行以下修改: - -```diff title=rspack.config.js -/** - * @type {import('@rspack/core').Configuration} - */ -const config = { - mode: 'production', - entry: { - index: './src/index.js', - }, - optimization: { - minimize: false, - moduleIds: 'named' - }, -+ module: { -+ rules: [ -+ { -+ test: /math\/add\.js/, -+ sideEffects: false -+ } -+ ] -+ } -}; -module.exports = config; -``` +由于能够分析出 `src/re-exports.js` 所有的重导出,可以知道 `src/value.js` 中的 `foo` 并未使用到,最终产物中 `foo` 会被删掉。 -重新构建项目,我们得到和之前相同的结果,整个 **math/add.js** 模块都被删除了。 +## 变量传递 -### Reexports optimization +有时候,即使导出被访问到,它们也可能并不会被实际使用到。例如: -开启 SideEffects 优化后,Rspack 还会尝试对重导出模块进行优化。 +```js title='src/main.js' +import { foo } from './value.js'; -```js title=sdk.js -export { a } from './a.js'; -export { b } from './b.js'; -export { c } from './c.js'; -// ... -``` +function log() { + console.log(foo); +} // `log` is not used -```js title=index.js -import { a } from './sdk.js'; -console.log(a); +const bar = foo; // `foo` is not used ``` -当 `sdk.js` 无副作用时,Rspack 会尝试直接从 `a.js` 中引入 `a`,而不引入 `sdk.js` 中重导出的其他模块。 +在上例中,即使 `log` 函数和 `bar` 变量依赖了 `foo`,由于他们自身未被使用到,`foo` 仍然可以被视为死代码并删除。 -在某些不规范的库中可能存在循环依赖,例如 `a.js` 从 `sdk.js` 中引入 `b.js` 中的成员。 +在开启 [innerGraph](/config/optimization#optimizationinnergraph) 优化后(在 `production` 模式下默认开启),针对跨模块的复杂情形,Rspack 依然能够追踪变量的使用情况,从而实现精确的代码优化。 -```js title=a.js -import { b } from './sdk.js'; -export const a = b; +```js title='src/main.js' +import { value } from './bar.js'; +console.log(value); ``` -```js title=index.js -import { a } from './sdk.js'; -console.log(a); +```js title='src/bar.js' +import { foo } from './foo.js'; +const bar = foo; +export const value = bar; ``` -此时 `a.js` 和 `sdk.js` 之间存在循环依赖,很不推荐有意这样利用循环依赖,但仍然可以构建成功,并且成功运行。这是因为 Rspack 会尝试将上述的引用转换成: - -```diff title=a.js -- import { b } from './sdk.js'; -+ import { b } from './b.js'; -export const a = b; -``` - -```diff title=index.js -- import { a } from './sdk.js' -+ import { a } from './a.js' -console.log(a); +```js title='src/foo.js' +export const foo = 42; ``` -转换后循环依赖没有了。但是如果你尝试关掉 `optimization.providedExports` 或 `optimization.sideEffects`,构建会成功,但运行时会因为循环依赖而遇到错误。 - -> 上述循环依赖的例子可以在[这里](https://github.com/rspack-contrib/rspack-examples/tree/main/rspack/rspack/treeshaking-transform-imports)找到。 - -这张图演示了重导出优化的过程: - -![](https://assets.rspack.dev/rspack/assets/rspack-reexports-optimization.png) +在这种情况下,由于 `value` 最终被使用,它所依赖的 `foo` 也得以保留。