Skip to content
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

Disable treeshaking per module #3663

Merged
merged 4 commits into from Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/05-plugin-development.md
Expand Up @@ -93,14 +93,14 @@ Next Hook: [`resolveId`](guide/en/#resolveid) to resolve each entry point in par
Called on each `rollup.rollup` build. This is the recommended hook to use when you need access to the options passed to `rollup.rollup()` as it takes the transformations by all [`options`](guide/en/#options) hooks into account and also contains the right default values for unset options.

#### `load`
Type: `(id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null }`<br>
Type: `(id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | null }`<br>
Kind: `async, first`<br>
Previous Hook: [`resolveId`](guide/en/#resolveid) or [`resolveDynamicImport`](guide/en/#resolvedynamicimport) where the loaded id was resolved.<br>
Next Hook: [`transform`](guide/en/#transform) to transform the loaded file.

Defines a custom loader. Returning `null` defers to other `load` functions (and eventually the default behavior of loading from the file system). To prevent additional parsing overhead in case e.g. this hook already used `this.parse` to generate an AST for some reason, this hook can optionally return a `{ code, ast, map }` object. The `ast` must be a standard ESTree AST with `start` and `end` properties for each node. If the transformation does not move code, you can preserve existing sourcemaps by setting `map` to `null`. Otherwise you might need to generate the source map. See [the section on source code transformations](#source-code-transformations).

If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included in the bundle without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`. The `transform` hook can override this.
If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included in the bundle without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `"no-treeshake"` is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`. The `transform` hook can override this.

If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. The `transform` hook can override this. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:

Expand Down Expand Up @@ -150,7 +150,7 @@ In case a dynamic import is not passed a string as argument, this hook gets acce
Note that the return value of this hook will not be passed to `resolveId` afterwards; if you need access to the static resolution algorithm, you can use [`this.resolve(source, importer)`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null) on the plugin context.

#### `resolveId`
Type: `(source: string, importer: string | undefined) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`<br>
Type: `(source: string, importer: string | undefined) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | null}`<br>
Kind: `async, first`<br>
Previous Hook: [`buildStart`](guide/en/#buildstart) if we are resolving an entry point, [`transform`](guide/en/#transform) if we are resolving an import, or as fallback for [`resolveDynamicImport`](guide/en/#resolvedynamicimport). Additionally this hook can be triggered during the build phase from plugin hooks by calling [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string) to emit an entry point or at any time by calling [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null) to manually resolve an id.<br>
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend).
Expand All @@ -170,7 +170,7 @@ resolveId(source) {

Relative ids, i.e. starting with `./` or `../`, will **not** be renormalized when returning an object. If you want this behaviour, return an absolute file system location as `id` instead.

If `false` is returned for `moduleSideEffects` in the first hook that resolves a module id and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the `treeshake.moduleSideEffects` option or default to `true`. The `load` and `transform` hooks can override this.
If `false` is returned for `moduleSideEffects` in the first hook that resolves a module id and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `"no-treeshake"` is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the `treeshake.moduleSideEffects` option or default to `true`. The `load` and `transform` hooks can override this.

If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. The `load` and `transform` hooks can override this. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:

Expand All @@ -191,7 +191,7 @@ console.log(foo, bar);
```

#### `transform`
Type: `(code: string, id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null }`<br>
Type: `(code: string, id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | null }`<br>
Kind: `async, sequential`<br>
Previous Hook: [`load`](guide/en/#load) where the currently handled file was loaded.<br>
NextHook: [`resolveId`](guide/en/#resolveid) and [`resolveDynamicImport`](guide/en/#resolvedynamicimport) to resolve all discovered static and dynamic imports in parallel if present, otherwise [`buildEnd`](guide/en/#buildend).
Expand All @@ -200,7 +200,7 @@ Can be used to transform individual modules. To prevent additional parsing overh

Note that in watch mode, the result of this hook is cached when rebuilding and the hook is only triggered again for a module `id` if either the `code` of the module has changed or a file has changed that was added via `this.addWatchFile` the last time the hook was triggered for this module.

If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`.
If `false` is returned for `moduleSideEffects` and no other module imports anything from this module, then this module will not be included without checking for actual side-effects inside the module. If `true` is returned, Rollup will use its default algorithm to include all statements in the module that have side-effects (such as modifying a global or exported variable). If `"no-treeshake"` is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If `null` is returned or the flag is omitted, then `moduleSideEffects` will be determined by the first `resolveId` hook that resolved this module, the `treeshake.moduleSideEffects` option, or eventually default to `true`.

If `true` is returned for `syntheticNamedExports`, this module will fallback the resolution of any missing named export to properties of the `default` export. This option allows to have dynamic named exports that might not be declared in the module, such as in this example:

Expand Down Expand Up @@ -628,7 +628,7 @@ Returns additional information about the module in question in the form
dynamicImporters: string[], // the ids of all modules that import this module via dynamic import()
implicitlyLoadedAfterOneOf: string[], // implicit relationships, declared via this.emitChunk
implicitlyLoadedBefore: string[], // implicit relationships, declared via this.emitChunk
hasModuleSideEffects: boolean // are imports of this module included if nothing is imported from it
hasModuleSideEffects: boolean | "no-treeshake" // are imports of this module included if nothing is imported from it
}
```

Expand Down
16 changes: 5 additions & 11 deletions docs/999-big-list-of-options.md
Expand Up @@ -371,8 +371,6 @@ The pattern to use for naming custom emitted assets to include in the build outp

Forward slashes `/` can be used to place files in sub-directories. When using a function, `assetInfo` is a reduced version of the one in [`generateBundle`](guide/en/#generatebundle) without the `fileName`. See also [`output.chunkFileNames`](guide/en/#outputchunkfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).

You can also supply a function that returns a pattern as string.

#### output.banner/output.footer
Type: `string | (() => string | Promise<string>)`<br>
CLI: `--banner`/`--footer <text>`
Expand Down Expand Up @@ -405,8 +403,6 @@ The pattern to use for naming shared chunks created when code-splitting, or a fu

Forward slashes `/` can be used to place files in sub-directories. When using a function, `chunkInfo` is a reduced version of the one in [`generateBundle`](guide/en/#generatebundle) without properties that depend on file names. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.entryFileNames`](guide/en/#outputentryfilenames).

You can also supply a function that returns a pattern as string.

#### output.compact
Type: `boolean`<br>
CLI: `--compact`/`--no-compact`<br>
Expand All @@ -426,20 +422,18 @@ The pattern to use for chunks created from entry points, or a function that is c

Forward slashes `/` can be used to place files in sub-directories. When using a function, `chunkInfo` is a reduced version of the one in [`generateBundle`](guide/en/#generatebundle) without properties that depend on file names. See also [`output.assetFileNames`](guide/en/#outputassetfilenames), [`output.chunkFileNames`](guide/en/#outputchunkfilenames).

This pattern will also be used when using the [`output.preserveModules`](guide/en/#outputpreservemodules) option. Here there is a different set of placeholders available, though:
This pattern will also be used when setting the [`output.preserveModules`](guide/en/#outputpreservemodules) option. Here a different set of placeholders is available, though:
* `[format]`: The rendering format defined in the output options.
* `[name]`: The file name (without extension) of the file.
* `[ext]`: The extension of the file.
* `[extname]`: The extension of the file, prefixed by `.` if it is not empty.

You can also supply a function that returns a pattern as string.

#### output.extend
Type: `boolean`<br>
CLI: `--extend`/`--no-extend`<br>
Default: `false`

Whether or not to extend the global variable defined by the `name` option in `umd` or `iife` formats. When `true`, the global variable will be defined as `(global.name = global.name || {})`. When false, the global defined by `name` will be overwritten like `(global.name = {})`.
Whether to extend the global variable defined by the `name` option in `umd` or `iife` formats. When `true`, the global variable will be defined as `(global.name = global.name || {})`. When false, the global defined by `name` will be overwritten like `(global.name = {})`.

#### output.hoistTransitiveImports
Type: `boolean`<br>
Expand All @@ -460,7 +454,7 @@ Type: `boolean`<br>
CLI: `--interop`/`--no-interop`<br>
Default: `true`

Whether or not to add an 'interop block'. By default (`interop: true`), for safety's sake, Rollup will assign any external dependencies' `default` exports to a separate variable if it is necessary to distinguish between default and named exports. This generally only applies if your external dependencies were transpiled (for example with Babel) – if you are sure you do not need it, you can save a few bytes with `interop: false`.
Whether to add an 'interop block'. By default (`interop: true`), for safety's sake, Rollup will assign any external dependencies' `default` exports to a separate variable if it is necessary to distinguish between default and named exports. This generally only applies if your external dependencies were transpiled (for example with Babel) – if you are sure you do not need it, you can save a few bytes with `interop: false`.

#### output.intro/output.outro
Type: `string | (() => string | Promise<string>)`<br>
Expand Down Expand Up @@ -919,7 +913,7 @@ Type: `boolean`<br>
CLI: `--esModule`/`--no-esModule`<br>
Default: `true`

Whether or not to add a `__esModule: true` property when generating exports for non-ES formats.
Whether to add a `__esModule: true` property when generating exports for non-ES formats.

#### output.exports
Type: `string`<br>
Expand Down Expand Up @@ -1096,7 +1090,7 @@ Type: `boolean | { annotations?: boolean, moduleSideEffects?: ModuleSideEffectsO
CLI: `--treeshake`/`--no-treeshake`<br>
Default: `true`

Whether or not to apply tree-shaking and to fine-tune the tree-shaking process. Setting this option to `false` will produce bigger bundles but may improve build performance. If you discover a bug caused by the tree-shaking algorithm, please file an issue!
Whether to apply tree-shaking and to fine-tune the tree-shaking process. Setting this option to `false` will produce bigger bundles but may improve build performance. If you discover a bug caused by the tree-shaking algorithm, please file an issue!
Setting this option to an object implies tree-shaking is enabled and grants the following additional options:

**treeshake.annotations**<br>
Expand Down
4 changes: 2 additions & 2 deletions src/ExternalModule.ts
Expand Up @@ -13,7 +13,7 @@ export default class ExternalModule {
exportsNamespace = false;
id: string;
importers: string[] = [];
moduleSideEffects: boolean;
moduleSideEffects: boolean | 'no-treeshake';
mostCommonSuggestion = 0;
nameSuggestions: { [name: string]: number };
reexported = false;
Expand All @@ -25,7 +25,7 @@ export default class ExternalModule {
constructor(
private readonly options: NormalizedInputOptions,
id: string,
moduleSideEffects: boolean
moduleSideEffects: boolean | 'no-treeshake'
) {
this.id = id;
this.execIndex = Infinity;
Expand Down
8 changes: 7 additions & 1 deletion src/Graph.ts
Expand Up @@ -196,7 +196,13 @@ export default class Graph {
timeStart(`treeshaking pass ${treeshakingPass}`, 3);
this.needsTreeshakingPass = false;
for (const module of this.modules) {
if (module.isExecuted) module.include();
if (module.isExecuted) {
if (module.moduleSideEffects === 'no-treeshake') {
module.includeAllInBundle();
} else {
module.include();
}
}
}
timeEnd(`treeshaking pass ${treeshakingPass++}`, 3);
} while (this.needsTreeshakingPass);
Expand Down
13 changes: 8 additions & 5 deletions src/Module.ts
Expand Up @@ -237,7 +237,7 @@ export default class Module {
private readonly graph: Graph,
public readonly id: string,
private readonly options: NormalizedInputOptions,
public moduleSideEffects: boolean,
public moduleSideEffects: boolean | 'no-treeshake',
public syntheticNamedExports: boolean,
public isEntryPoint: boolean
) {
Expand Down Expand Up @@ -328,7 +328,7 @@ export default class Module {
}
relevantDependencies.add(variable.module!);
}
if (this.options.treeshake) {
if (this.options.treeshake && this.moduleSideEffects !== 'no-treeshake') {
for (const dependency of possibleDependencies) {
if (
!(
Expand Down Expand Up @@ -500,7 +500,10 @@ export default class Module {
}

hasEffects() {
return this.ast.included && this.ast.hasEffects(createHasEffectsContext());
return (
this.moduleSideEffects === 'no-treeshake' ||
(this.ast.included && this.ast.hasEffects(createHasEffectsContext()))
);
}

include(): void {
Expand Down Expand Up @@ -604,10 +607,10 @@ export default class Module {
}
this.transformDependencies = transformDependencies;
this.customTransformCache = customTransformCache;
if (typeof moduleSideEffects === 'boolean') {
if (moduleSideEffects != null) {
this.moduleSideEffects = moduleSideEffects;
}
if (typeof syntheticNamedExports === 'boolean') {
if (syntheticNamedExports != null) {
this.syntheticNamedExports = syntheticNamedExports;
}

Expand Down
17 changes: 7 additions & 10 deletions src/ModuleLoader.ts
Expand Up @@ -207,10 +207,10 @@ export class ModuleLoader {
}
module.setSource(cachedModule);
} else {
if (typeof sourceDescription.moduleSideEffects === 'boolean') {
if (sourceDescription.moduleSideEffects != null) {
module.moduleSideEffects = sourceDescription.moduleSideEffects;
}
if (typeof sourceDescription.syntheticNamedExports === 'boolean') {
if (sourceDescription.syntheticNamedExports != null) {
module.syntheticNamedExports = sourceDescription.syntheticNamedExports;
}
module.setSource(
Expand Down Expand Up @@ -269,7 +269,7 @@ export class ModuleLoader {
private async fetchModule(
id: string,
importer: string | undefined,
moduleSideEffects: boolean,
moduleSideEffects: boolean | 'no-treeshake',
syntheticNamedExports: boolean,
isEntry: boolean
): Promise<Module> {
Expand Down Expand Up @@ -418,18 +418,18 @@ export class ModuleLoader {
): ResolvedId | null {
let id = '';
let external = false;
let moduleSideEffects = null;
let moduleSideEffects: boolean | 'no-treeshake' | null = null;
let syntheticNamedExports = false;
if (resolveIdResult) {
if (typeof resolveIdResult === 'object') {
id = resolveIdResult.id;
if (resolveIdResult.external) {
external = true;
}
if (typeof resolveIdResult.moduleSideEffects === 'boolean') {
if (resolveIdResult.moduleSideEffects != null) {
moduleSideEffects = resolveIdResult.moduleSideEffects;
}
if (typeof resolveIdResult.syntheticNamedExports === 'boolean') {
if (resolveIdResult.syntheticNamedExports != null) {
syntheticNamedExports = resolveIdResult.syntheticNamedExports;
}
} else {
Expand All @@ -448,10 +448,7 @@ export class ModuleLoader {
return {
external,
id,
moduleSideEffects:
typeof moduleSideEffects === 'boolean'
? moduleSideEffects
: this.hasModuleSideEffects(id, external),
moduleSideEffects: moduleSideEffects ?? this.hasModuleSideEffects(id, external),
syntheticNamedExports
};
}
Expand Down