From 77d9b4a152cb7065a6eee23aba5614c923da9d73 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 1 Feb 2022 09:02:25 +0100 Subject: [PATCH] Deprecate hasModuleSideEffects in favor of moduleSideEffects and ensure it is mutable on ModuleInfo --- docs/05-plugin-development.md | 59 ++++++++---- src/Chunk.ts | 2 +- src/ExternalModule.ts | 22 ++++- src/Graph.ts | 2 +- src/Module.ts | 59 ++++++++---- src/ModuleLoader.ts | 2 +- src/rollup/types.d.ts | 5 +- src/utils/traverseStaticDependencies.ts | 2 +- .../empty-module-no-treeshake/_config.js | 68 ++++++++++++++ .../amd/generated-emptyTransformed.js | 5 ++ .../_expected/amd/main1.js | 5 ++ .../_expected/amd/main2.js | 5 ++ .../cjs/generated-emptyTransformed.js | 2 + .../_expected/cjs/main1.js | 5 ++ .../_expected/cjs/main2.js | 5 ++ .../es/generated-emptyTransformed.js | 1 + .../_expected/es/main1.js | 3 + .../_expected/es/main2.js | 3 + .../system/generated-emptyTransformed.js | 10 +++ .../_expected/system/main1.js | 11 +++ .../_expected/system/main2.js | 11 +++ .../empty-module-no-treeshake/main1.js | 4 + .../empty-module-no-treeshake/main2.js | 4 + .../empty-module-no-treeshake/_config.js | 2 +- .../_config.js | 4 +- .../implicitly-dependent-entry/_config.js | 4 +- .../multiple-dependencies/_config.js | 6 +- .../single-dependency/_config.js | 4 +- .../deprecated/manual-chunks-info/_config.js | 8 +- .../deprecated/resolve-id-external/_config.js | 86 ++++++++++++++++++ .../deprecated/resolve-id-external/main.js | 25 ++++++ .../samples/deprecated/resolve-id/_config.js | 89 +++++++++++++++++++ .../samples/deprecated/resolve-id/main.js | 25 ++++++ .../dynamicImportFunction/main.js | 12 +-- .../hasModuleSideEffects/_config.js | 26 ++++++ .../deprecations/hasModuleSideEffects/main.js | 1 + .../samples/manual-chunks-info/_config.js | 8 +- .../samples/module-parsed-hook/_config.js | 4 +- .../resolve-id-external/_config.js | 41 ++++----- .../module-side-effects/resolve-id/_config.js | 43 +++++---- .../module-side-effects/writable/_config.js | 72 +++++++++++++++ .../module-side-effects/writable/foo.js | 1 + .../module-side-effects/writable/main.js | 2 + .../plugin-module-information/_config.js | 10 +-- .../samples/preload-module/_config.js | 4 +- 45 files changed, 644 insertions(+), 128 deletions(-) create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_config.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/generated-emptyTransformed.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main1.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main2.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/generated-emptyTransformed.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main1.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main2.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/generated-emptyTransformed.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main1.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main2.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/generated-emptyTransformed.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main1.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main2.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/main1.js create mode 100644 test/chunking-form/samples/deprecated/empty-module-no-treeshake/main2.js create mode 100644 test/function/samples/deprecated/resolve-id-external/_config.js create mode 100644 test/function/samples/deprecated/resolve-id-external/main.js create mode 100644 test/function/samples/deprecated/resolve-id/_config.js create mode 100644 test/function/samples/deprecated/resolve-id/main.js create mode 100644 test/function/samples/deprecations/hasModuleSideEffects/_config.js create mode 100644 test/function/samples/deprecations/hasModuleSideEffects/main.js create mode 100644 test/function/samples/module-side-effects/writable/_config.js create mode 100644 test/function/samples/module-side-effects/writable/foo.js create mode 100644 test/function/samples/module-side-effects/writable/main.js diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index 2f02507230e..17a590e4b00 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -167,33 +167,58 @@ For those cases, the `isEntry` option will tell you if we are resolving a user d You can use this for instance as a mechanism to define custom proxy modules for entry points. The following plugin will proxy all entry points to inject a polyfill import. ```js +// We prefix the polyfill id with \0 to tell other plugins not to try to load or +// transform it +const POLYFILL_ID = '\0polyfill'; +const PROXY_SUFFIX = '?inject-polyfill-proxy'; + function injectPolyfillPlugin() { return { name: 'inject-polyfill', async resolveId(source, importer, options) { + if (source === POLYFILL_ID) { + // It is important that side effects are always respected for polyfills, + // otherwise using "treeshake.moduleSideEffects: false" may prevent the + // polyfill from being included. + return { id: POLYFILL_ID, moduleSideEffects: true }; + } if (options.isEntry) { - // We need to skip this plugin to avoid an infinite loop + // Determine what the actual entry would have been. We need "skipSelf" + // to avoid an infinite loop. const resolution = await this.resolve(source, importer, { skipSelf: true, ...options }); // If it cannot be resolved or is external, just return it so that // Rollup can display an error if (!resolution || resolution.external) return resolution; - // In the load hook of the proxy, we want to use this.load to find out - // if the entry has a default export. In the load hook, however, we no - // longer have the full "resolution" object that may contain meta-data - // from other plugins that is only added on first load. Therefore we - // trigger loading here without waiting for it. - this.load(resolution); - return `${resolution.id}?entry-proxy`; + // In the load hook of the proxy, we need to know if the entry has a + // default export. There, however, we no longer have the full + // "resolution" object that may contain meta-data from other plugins + // that is only added on first load. Therefore we trigger loading here. + const moduleInfo = await this.load(resolution); + // We need to make sure side effects in the original entry point + // are respected even for treeshake.moduleSideEffects: false. + // "moduleSideEffects" is a writable property on ModuleInfo. + moduleInfo.moduleSideEffects = true; + // It is important that the new entry does not start with \0 and + // has the same directory as the original one to not mess up + // relative external import generation. Also keeping the name and + // just adding a "?query" to the end ensures that preserveModules + // will generate the original entry name for this entry. + return `${resolution.id}${PROXY_SUFFIX}`; } return null; }, - async load(id) { - if (id.endsWith('?entry-proxy')) { - const entryId = id.slice(0, -'?entry-proxy'.length); - // We need to load and parse the original entry first because we need - // to know if it has a default export - const { hasDefaultExport } = await this.load({ id: entryId }); - let code = `import 'polyfill';export * from ${JSON.stringify(entryId)};`; + load(id) { + if (id === POLYFILL_ID) { + // Replace with actual polyfill + return "console.log('polyfill');"; + } + if (id.endsWith(PROXY_SUFFIX)) { + const entryId = id.slice(0, -PROXY_SUFFIX.length); + // We know ModuleInfo.hasDefaultExport is reliable because we awaited + // this.load in resolveId + const { hasDefaultExport } = this.getModuleInfo(entryId); + let code = + `import ${JSON.stringify(POLYFILL_ID)};` + `export * from ${JSON.stringify(entryId)};`; // Namespace reexports do not reexport default, so we need special // handling here if (hasDefaultExport) { @@ -700,8 +725,8 @@ type ModuleInfo = { dynamicImporters: string[]; // the ids of all modules that import this module via dynamic import() implicitlyLoadedAfterOneOf: string[]; // implicit relationships, declared via this.emitFile implicitlyLoadedBefore: string[]; // implicit relationships, declared via this.emitFile - hasModuleSideEffects: boolean | 'no-treeshake'; // are imports of this module included if nothing is imported from it meta: { [plugin: string]: any }; // custom module meta-data + moduleSideEffects: boolean | 'no-treeshake'; // are imports of this module included if nothing is imported from it syntheticNamedExports: boolean | string; // final value of synthetic named exports }; @@ -716,6 +741,8 @@ type ResolvedId = { During the build, this object represents currently available information about the module. Before the [`buildEnd`](guide/en/#buildend) hook, this information may be incomplete as e.g. the `importedIds` are not yet resolved or additional `importers` are discovered. +Note that while most properties are read-only, `moduleSideEffects` is writable and changes will be picked up if they occur before the `buildEnd` hook is triggered. `meta` should not be overwritten but it is ok to mutate its properties at any time to store meta information about a module. The advantage of doing this instead of keeping state in a plugin is that `meta` is persisted to and restored from the cache if it is used, e.g. when using watch mode from the CLI. + Returns `null` if the module id cannot be found. #### `this.getWatchFiles` diff --git a/src/Chunk.ts b/src/Chunk.ts index 4fd0b855972..a19b881048f 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -241,7 +241,7 @@ export default class Chunk { } if ( !chunk.dependencies.has(chunkByModule.get(facadedModule)!) && - facadedModule.info.hasModuleSideEffects && + facadedModule.info.moduleSideEffects && facadedModule.hasEffects() ) { chunk.dependencies.add(chunkByModule.get(facadedModule)!); diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index e83f5409a9d..b1862720bb8 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -6,6 +6,7 @@ import type { NormalizedOutputOptions } from './rollup/types'; import { EMPTY_ARRAY } from './utils/blank'; +import { warnDeprecation } from './utils/error'; import { makeLegal } from './utils/identifierHelpers'; import { normalize, relative } from './utils/path'; import { printQuotedStringList } from './utils/printStringList'; @@ -31,14 +32,14 @@ export default class ExternalModule { constructor( private readonly options: NormalizedInputOptions, public readonly id: string, - hasModuleSideEffects: boolean | 'no-treeshake', + moduleSideEffects: boolean | 'no-treeshake', meta: CustomPluginOptions, public readonly renormalizeRenderPath: boolean ) { this.suggestedVariableName = makeLegal(id.split(/[\\/]/).pop()!); const { importers, dynamicImporters } = this; - this.info = { + const info: ModuleInfo = (this.info = { ast: null, code: null, dynamicallyImportedIdResolutions: EMPTY_ARRAY, @@ -47,7 +48,14 @@ export default class ExternalModule { return dynamicImporters.sort(); }, hasDefaultExport: null, - hasModuleSideEffects, + get hasModuleSideEffects() { + warnDeprecation( + 'Accessing ModuleInfo.hasModuleSideEffects from plugins is deprecated. Please use ModuleInfo.moduleSideEffects instead.', + false, + options + ); + return info.moduleSideEffects; + }, id, implicitlyLoadedAfterOneOf: EMPTY_ARRAY, implicitlyLoadedBefore: EMPTY_ARRAY, @@ -60,8 +68,14 @@ export default class ExternalModule { isExternal: true, isIncluded: null, meta, + moduleSideEffects, syntheticNamedExports: false - }; + }); + // Hide the deprecated key so that it only warns when accessed explicitly + Object.defineProperty(this.info, 'hasModuleSideEffects', { + ...Object.getOwnPropertyDescriptor(this.info, 'hasModuleSideEffects'), + enumerable: false + }); } getVariableForExportName(name: string): [variable: ExternalVariable] { diff --git a/src/Graph.ts b/src/Graph.ts index 38afb11c095..e8633db84f4 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -189,7 +189,7 @@ export default class Graph { this.needsTreeshakingPass = false; for (const module of this.modules) { if (module.isExecuted) { - if (module.info.hasModuleSideEffects === 'no-treeshake') { + if (module.info.moduleSideEffects === 'no-treeshake') { module.includeAllInBundle(); } else { module.include(); diff --git a/src/Module.ts b/src/Module.ts index 0c9d09f1a63..d9f454bd853 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -54,7 +54,8 @@ import { errMissingExport, errNamespaceConflict, error, - errSyntheticNamedExportsNeedNamespaceExport + errSyntheticNamedExportsNeedNamespaceExport, + warnDeprecation } from './utils/error'; import { getId } from './utils/getId'; import { getOrCreate } from './utils/getOrCreate'; @@ -247,7 +248,7 @@ export default class Module { public readonly id: string, private readonly options: NormalizedInputOptions, isEntry: boolean, - hasModuleSideEffects: boolean | 'no-treeshake', + moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: CustomPluginOptions ) { @@ -257,17 +258,26 @@ export default class Module { // eslint-disable-next-line @typescript-eslint/no-this-alias const module = this; + const { + dynamicImports, + dynamicImporters, + reexportDescriptions, + implicitlyLoadedAfter, + implicitlyLoadedBefore, + sources, + importers + } = this; this.info = { ast: null, code: null, get dynamicallyImportedIdResolutions() { - return module.dynamicImports + return dynamicImports .map(({ argument }) => typeof argument === 'string' && module.resolvedIds[argument]) .filter(Boolean) as ResolvedId[]; }, get dynamicallyImportedIds() { const dynamicallyImportedIds: string[] = []; - for (const { id } of module.dynamicImports) { + for (const { id } of dynamicImports) { if (id) { dynamicallyImportedIds.push(id); } @@ -275,43 +285,56 @@ export default class Module { return dynamicallyImportedIds; }, get dynamicImporters() { - return module.dynamicImporters.sort(); + return dynamicImporters.sort(); }, get hasDefaultExport() { // This information is only valid after parsing if (!module.ast) { return null; } - return 'default' in module.exports || 'default' in module.reexportDescriptions; + return 'default' in module.exports || 'default' in reexportDescriptions; + }, + get hasModuleSideEffects() { + warnDeprecation( + 'Accessing ModuleInfo.hasModuleSideEffects from plugins is deprecated. Please use ModuleInfo.moduleSideEffects instead.', + false, + options + ); + return module.info.moduleSideEffects; }, - hasModuleSideEffects, id, get implicitlyLoadedAfterOneOf() { - return Array.from(module.implicitlyLoadedAfter, getId).sort(); + return Array.from(implicitlyLoadedAfter, getId).sort(); }, get implicitlyLoadedBefore() { - return Array.from(module.implicitlyLoadedBefore, getId).sort(); + return Array.from(implicitlyLoadedBefore, getId).sort(); }, get importedIdResolutions() { - return Array.from(module.sources, source => module.resolvedIds[source]).filter(Boolean); + return Array.from(sources, source => module.resolvedIds[source]).filter(Boolean); }, get importedIds() { - return Array.from(module.sources, source => module.resolvedIds[source]?.id).filter(Boolean); + return Array.from(sources, source => module.resolvedIds[source]?.id).filter(Boolean); }, get importers() { - return module.importers.sort(); + return importers.sort(); }, isEntry, isExternal: false, get isIncluded() { - if (module.graph.phase !== BuildPhase.GENERATE) { + if (graph.phase !== BuildPhase.GENERATE) { return null; } return module.isIncluded(); }, meta: { ...meta }, + moduleSideEffects, syntheticNamedExports }; + // Hide the deprecated key so that it only warns when accessed explicitly + Object.defineProperty(this.info, 'hasModuleSideEffects', { + ...Object.getOwnPropertyDescriptor(this.info, 'hasModuleSideEffects'), + enumerable: false + }); } basename(): string { @@ -393,7 +416,7 @@ export default class Module { } necessaryDependencies.add(variable.module!); } - if (!this.options.treeshake || this.info.hasModuleSideEffects === 'no-treeshake') { + if (!this.options.treeshake || this.info.moduleSideEffects === 'no-treeshake') { for (const dependency of this.dependencies) { relevantDependencies.add(dependency); } @@ -600,7 +623,7 @@ export default class Module { hasEffects(): boolean { return ( - this.info.hasModuleSideEffects === 'no-treeshake' || + this.info.moduleSideEffects === 'no-treeshake' || (this.ast!.included && this.ast!.hasEffects(createHasEffectsContext())) ); } @@ -766,7 +789,7 @@ export default class Module { dependencies: Array.from(this.dependencies, getId), id: this.id, meta: this.info.meta, - moduleSideEffects: this.info.hasModuleSideEffects, + moduleSideEffects: this.info.moduleSideEffects, originalCode: this.originalCode, originalSourcemap: this.originalSourcemap, resolvedIds: this.resolvedIds, @@ -835,7 +858,7 @@ export default class Module { syntheticNamedExports }: Partial>): void { if (moduleSideEffects != null) { - this.info.hasModuleSideEffects = moduleSideEffects; + this.info.moduleSideEffects = moduleSideEffects; } if (syntheticNamedExports != null) { this.info.syntheticNamedExports = syntheticNamedExports; @@ -1008,7 +1031,7 @@ export default class Module { relevantDependencies.add(dependency); continue; } - if (!(dependency.info.hasModuleSideEffects || alwaysCheckedDependencies.has(dependency))) { + if (!(dependency.info.moduleSideEffects || alwaysCheckedDependencies.has(dependency))) { continue; } if (dependency instanceof ExternalModule || dependency.hasEffects()) { diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index 831a96fe153..f1455ce43c1 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -444,7 +444,7 @@ export class ModuleLoader { module.dependencies.add(dependency); dependency.importers.push(module.id); } - if (!this.options.treeshake || module.info.hasModuleSideEffects === 'no-treeshake') { + if (!this.options.treeshake || module.info.moduleSideEffects === 'no-treeshake') { for (const dependency of module.dependencies) { if (dependency instanceof Module) { dependency.importedFromNotTreeshaken = true; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index f1b6307eecf..baf2b6c553a 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -156,13 +156,14 @@ export type EmitChunk = (id: string, options?: { name?: string }) => string; export type EmitFile = (emittedFile: EmittedFile) => string; -interface ModuleInfo { +interface ModuleInfo extends ModuleOptions { ast: AcornNode | null; code: string | null; dynamicImporters: readonly string[]; dynamicallyImportedIdResolutions: readonly ResolvedId[]; dynamicallyImportedIds: readonly string[]; hasDefaultExport: boolean | null; + /** @deprecated Use `moduleSideEffects` instead */ hasModuleSideEffects: boolean | 'no-treeshake'; id: string; implicitlyLoadedAfterOneOf: readonly string[]; @@ -173,8 +174,6 @@ interface ModuleInfo { isEntry: boolean; isExternal: boolean; isIncluded: boolean | null; - meta: CustomPluginOptions; - syntheticNamedExports: boolean | string; } export type GetModuleInfo = (moduleId: string) => ModuleInfo | null; diff --git a/src/utils/traverseStaticDependencies.ts b/src/utils/traverseStaticDependencies.ts index 54a3bb42aca..1f88696a267 100644 --- a/src/utils/traverseStaticDependencies.ts +++ b/src/utils/traverseStaticDependencies.ts @@ -10,7 +10,7 @@ export function markModuleAndImpureDependenciesAsExecuted(baseModule: Module): v if ( !(dependency instanceof ExternalModule) && !dependency.isExecuted && - (dependency.info.hasModuleSideEffects || module.implicitlyLoadedBefore.has(dependency)) && + (dependency.info.moduleSideEffects || module.implicitlyLoadedBefore.has(dependency)) && !visitedModules.has(dependency.id) ) { dependency.isExecuted = true; diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_config.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_config.js new file mode 100644 index 00000000000..182023bccc3 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_config.js @@ -0,0 +1,68 @@ +const assert = require('assert'); +const path = require('path'); +const { getObject } = require('../../../../utils'); + +module.exports = { + description: 'associates empty modules with chunks if tree-shaking is disabled for them', + options: { + strictDeprecations: false, + input: ['main1.js', 'main2.js'], + plugins: { + resolveId(id) { + if (id.startsWith('empty')) { + if (id === 'emptyResolved') { + return { + id, + moduleSideEffects: 'no-treeshake' + }; + } + return id; + } + }, + load(id) { + if (id.startsWith('empty')) { + if (id === 'emptyLoaded') { + return { code: '', moduleSideEffects: 'no-treeshake' }; + } + return ''; + } + }, + transform(code, id) { + if (id === 'emptyTransformed') { + return { code: '', moduleSideEffects: 'no-treeshake' }; + } + }, + generateBundle(options, bundle) { + assert.deepStrictEqual( + getObject( + Array.from(this.getModuleIds(), id => [ + id.startsWith('empty') ? id : path.relative(__dirname, id), + this.getModuleInfo(id).hasModuleSideEffects + ]) + ), + { + empty: true, + emptyLoaded: 'no-treeshake', + emptyResolved: 'no-treeshake', + emptyTransformed: 'no-treeshake', + 'main1.js': true, + 'main2.js': true + } + ); + assert.deepStrictEqual( + getObject( + Object.entries(bundle).map(([chunkId, chunk]) => [ + chunkId, + Object.keys(chunk.modules).map(moduleId => path.relative(__dirname, moduleId)) + ]) + ), + { + 'main1.js': ['emptyResolved', 'main1.js'], + 'main2.js': ['emptyLoaded', 'main2.js'], + 'generated-emptyTransformed.js': ['emptyTransformed'] + } + ); + } + } + } +}; diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/generated-emptyTransformed.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/generated-emptyTransformed.js new file mode 100644 index 00000000000..a9e9419e212 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/generated-emptyTransformed.js @@ -0,0 +1,5 @@ +define((function () { 'use strict'; + + + +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main1.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main1.js new file mode 100644 index 00000000000..79b7506c8d3 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main1.js @@ -0,0 +1,5 @@ +define(['./generated-emptyTransformed'], (function (emptyTransformed) { 'use strict'; + + console.log('main1'); + +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main2.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main2.js new file mode 100644 index 00000000000..5637dfe6a34 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/amd/main2.js @@ -0,0 +1,5 @@ +define(['./generated-emptyTransformed'], (function (emptyTransformed) { 'use strict'; + + console.log('main2'); + +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/generated-emptyTransformed.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/generated-emptyTransformed.js new file mode 100644 index 00000000000..eb109abbed0 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/generated-emptyTransformed.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main1.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main1.js new file mode 100644 index 00000000000..12fe5960962 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main1.js @@ -0,0 +1,5 @@ +'use strict'; + +require('./generated-emptyTransformed.js'); + +console.log('main1'); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main2.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main2.js new file mode 100644 index 00000000000..1e027570150 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/cjs/main2.js @@ -0,0 +1,5 @@ +'use strict'; + +require('./generated-emptyTransformed.js'); + +console.log('main2'); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/generated-emptyTransformed.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/generated-emptyTransformed.js new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/generated-emptyTransformed.js @@ -0,0 +1 @@ + diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main1.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main1.js new file mode 100644 index 00000000000..3e33abdecc1 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main1.js @@ -0,0 +1,3 @@ +import './generated-emptyTransformed.js'; + +console.log('main1'); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main2.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main2.js new file mode 100644 index 00000000000..897b460adf3 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/es/main2.js @@ -0,0 +1,3 @@ +import './generated-emptyTransformed.js'; + +console.log('main2'); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/generated-emptyTransformed.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/generated-emptyTransformed.js new file mode 100644 index 00000000000..343cc3a5100 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/generated-emptyTransformed.js @@ -0,0 +1,10 @@ +System.register([], (function () { + 'use strict'; + return { + execute: (function () { + + + + }) + }; +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main1.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main1.js new file mode 100644 index 00000000000..8e4b0d53d4c --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main1.js @@ -0,0 +1,11 @@ +System.register(['./generated-emptyTransformed.js'], (function () { + 'use strict'; + return { + setters: [function () {}], + execute: (function () { + + console.log('main1'); + + }) + }; +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main2.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main2.js new file mode 100644 index 00000000000..88a05aab011 --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/_expected/system/main2.js @@ -0,0 +1,11 @@ +System.register(['./generated-emptyTransformed.js'], (function () { + 'use strict'; + return { + setters: [function () {}], + execute: (function () { + + console.log('main2'); + + }) + }; +})); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main1.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main1.js new file mode 100644 index 00000000000..a8e85af413b --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main1.js @@ -0,0 +1,4 @@ +import 'empty'; +import 'emptyResolved'; +import 'emptyTransformed'; +console.log('main1'); diff --git a/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main2.js b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main2.js new file mode 100644 index 00000000000..6fe715b255d --- /dev/null +++ b/test/chunking-form/samples/deprecated/empty-module-no-treeshake/main2.js @@ -0,0 +1,4 @@ +import 'empty'; +import 'emptyLoaded'; +import 'emptyTransformed'; +console.log('main2'); diff --git a/test/chunking-form/samples/empty-module-no-treeshake/_config.js b/test/chunking-form/samples/empty-module-no-treeshake/_config.js index da96284354d..53472066881 100644 --- a/test/chunking-form/samples/empty-module-no-treeshake/_config.js +++ b/test/chunking-form/samples/empty-module-no-treeshake/_config.js @@ -36,7 +36,7 @@ module.exports = { getObject( Array.from(this.getModuleIds(), id => [ id.startsWith('empty') ? id : path.relative(__dirname, id), - this.getModuleInfo(id).hasModuleSideEffects + this.getModuleInfo(id).moduleSideEffects ]) ), { diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js index 0651fc7ad26..7d43493ea6d 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js @@ -74,7 +74,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -145,7 +145,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js index e25d1c64c4d..3c0a5fd5345 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js @@ -70,7 +70,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -141,7 +141,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js index 8802c21bf44..6abadc7fdce 100644 --- a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js @@ -118,7 +118,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN1, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [ID_DEP], @@ -238,7 +238,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN2, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [ID_DEP], @@ -357,7 +357,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [ID_MAIN1, ID_MAIN2], implicitlyLoadedBefore: [], diff --git a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js index 14c405191ee..0a1b56b3f85 100644 --- a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js @@ -69,7 +69,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [ID_DEP], @@ -140,7 +140,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [ID_MAIN], implicitlyLoadedBefore: [], diff --git a/test/function/samples/deprecated/manual-chunks-info/_config.js b/test/function/samples/deprecated/manual-chunks-info/_config.js index 7749e615892..f3f097c77b1 100644 --- a/test/function/samples/deprecated/manual-chunks-info/_config.js +++ b/test/function/samples/deprecated/manual-chunks-info/_config.js @@ -112,7 +112,7 @@ module.exports = { dynamicallyImportedIds: [getId('dynamic')], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -147,7 +147,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [getId('dynamic')], hasDefaultExport: null, - hasModuleSideEffects: true, + moduleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -180,7 +180,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: true, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -264,7 +264,7 @@ module.exports = { dynamicallyImportedIds: ['external'], dynamicImporters: [getId('main')], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/function/samples/deprecated/resolve-id-external/_config.js b/test/function/samples/deprecated/resolve-id-external/_config.js new file mode 100644 index 00000000000..8a7a8715ba6 --- /dev/null +++ b/test/function/samples/deprecated/resolve-id-external/_config.js @@ -0,0 +1,86 @@ +const assert = require('assert'); +const path = require('path'); +const sideEffects = []; + +module.exports = { + description: 'does not include modules without used exports if moduleSideEffect is false', + context: { + require(id) { + sideEffects.push(id); + return { value: id }; + } + }, + exports() { + assert.deepStrictEqual(sideEffects, [ + 'sideeffects-false-usereffects-false-used-import', + 'sideeffects-null-usereffects-false-used-import', + 'sideeffects-true-usereffects-false', + 'sideeffects-true-usereffects-false-unused-import', + 'sideeffects-true-usereffects-false-used-import', + 'sideeffects-false-usereffects-true-used-import', + 'sideeffects-null-usereffects-true', + 'sideeffects-null-usereffects-true-unused-import', + 'sideeffects-null-usereffects-true-used-import', + 'sideeffects-true-usereffects-true', + 'sideeffects-true-usereffects-true-unused-import', + 'sideeffects-true-usereffects-true-used-import' + ]); + }, + options: { + strictDeprecations: false, + treeshake: { + moduleSideEffects(id) { + if (id.includes('main')) return true; + return JSON.parse(id.split('-')[3]); + } + }, + plugins: { + name: 'test-plugin', + resolveId(id) { + if (!path.isAbsolute(id)) { + return { + id, + external: true, + moduleSideEffects: JSON.parse(id.split('-')[1]) + }; + } + }, + buildEnd() { + assert.deepStrictEqual( + Array.from(this.getModuleIds()) + .filter(id => !path.isAbsolute(id)) + .sort() + .map(id => ({ id, hasModuleSideEffects: this.getModuleInfo(id).hasModuleSideEffects })), + [ + { id: 'sideeffects-false-usereffects-false', hasModuleSideEffects: false }, + { + id: 'sideeffects-false-usereffects-false-unused-import', + hasModuleSideEffects: false + }, + { id: 'sideeffects-false-usereffects-false-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-unused-import', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-unused-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-true', hasModuleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-used-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-used-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-used-import', hasModuleSideEffects: true } + ] + ); + } + } + }, + warnings(warnings) { + for (const warning of warnings) { + assert.strictEqual(warning.code, 'UNUSED_EXTERNAL_IMPORT'); + } + } +}; diff --git a/test/function/samples/deprecated/resolve-id-external/main.js b/test/function/samples/deprecated/resolve-id-external/main.js new file mode 100644 index 00000000000..dba6f1c07dc --- /dev/null +++ b/test/function/samples/deprecated/resolve-id-external/main.js @@ -0,0 +1,25 @@ +import 'sideeffects-false-usereffects-false'; +import { value as unusedValue1 } from 'sideeffects-false-usereffects-false-unused-import'; +import { value as usedValue1 } from 'sideeffects-false-usereffects-false-used-import'; + +import 'sideeffects-null-usereffects-false'; +import { value as unusedValue2 } from 'sideeffects-null-usereffects-false-unused-import'; +import { value as usedValue2 } from 'sideeffects-null-usereffects-false-used-import'; + +import 'sideeffects-true-usereffects-false'; +import { value as unusedValue3 } from 'sideeffects-true-usereffects-false-unused-import'; +import { value as usedValue3 } from 'sideeffects-true-usereffects-false-used-import'; + +import 'sideeffects-false-usereffects-true'; +import { value as unusedValue4 } from 'sideeffects-false-usereffects-true-unused-import'; +import { value as usedValue4 } from 'sideeffects-false-usereffects-true-used-import'; + +import 'sideeffects-null-usereffects-true'; +import { value as unusedValue5 } from 'sideeffects-null-usereffects-true-unused-import'; +import { value as usedValue5 } from 'sideeffects-null-usereffects-true-used-import'; + +import 'sideeffects-true-usereffects-true'; +import { value as unusedValue6 } from 'sideeffects-true-usereffects-true-unused-import'; +import { value as usedValue6 } from 'sideeffects-true-usereffects-true-used-import'; + +export const values = [usedValue1, usedValue2, usedValue3, usedValue4, usedValue5, usedValue6]; diff --git a/test/function/samples/deprecated/resolve-id/_config.js b/test/function/samples/deprecated/resolve-id/_config.js new file mode 100644 index 00000000000..4367c729d60 --- /dev/null +++ b/test/function/samples/deprecated/resolve-id/_config.js @@ -0,0 +1,89 @@ +const assert = require('assert'); +const path = require('path'); +const sideEffects = []; + +module.exports = { + description: 'does not include modules without used exports if moduleSideEffect is false', + context: { + sideEffects + }, + exports() { + assert.deepStrictEqual(sideEffects, [ + 'sideeffects-false-usereffects-false-used-import', + 'sideeffects-null-usereffects-false-used-import', + 'sideeffects-true-usereffects-false', + 'sideeffects-true-usereffects-false-unused-import', + 'sideeffects-true-usereffects-false-used-import', + 'sideeffects-false-usereffects-true-used-import', + 'sideeffects-null-usereffects-true', + 'sideeffects-null-usereffects-true-unused-import', + 'sideeffects-null-usereffects-true-used-import', + 'sideeffects-true-usereffects-true', + 'sideeffects-true-usereffects-true-unused-import', + 'sideeffects-true-usereffects-true-used-import' + ]); + }, + options: { + strictDeprecations: false, + treeshake: { + moduleSideEffects(id) { + if (id.includes('main')) return true; + return JSON.parse(id.split('-')[3]); + } + }, + plugins: { + name: 'test-plugin', + resolveId(id) { + if (!path.isAbsolute(id)) { + return { + id, + external: false, + moduleSideEffects: JSON.parse(id.split('-')[1]) + }; + } + }, + load(id) { + if (!path.isAbsolute(id)) { + const sideEffects = JSON.parse(id.split('-')[1]); + const userEffects = JSON.parse(id.split('-')[3]); + assert.strictEqual( + this.getModuleInfo(id).hasModuleSideEffects, + typeof sideEffects === 'boolean' ? sideEffects : userEffects + ); + return `export const value = '${id}'; sideEffects.push(value);`; + } + }, + buildEnd() { + assert.deepStrictEqual( + Array.from(this.getModuleIds()) + .filter(id => !path.isAbsolute(id)) + .sort() + .map(id => ({ id, hasModuleSideEffects: this.getModuleInfo(id).hasModuleSideEffects })), + [ + { id: 'sideeffects-false-usereffects-false', hasModuleSideEffects: false }, + { + id: 'sideeffects-false-usereffects-false-unused-import', + hasModuleSideEffects: false + }, + { id: 'sideeffects-false-usereffects-false-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-unused-import', hasModuleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-unused-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-used-import', hasModuleSideEffects: false }, + { id: 'sideeffects-null-usereffects-true', hasModuleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-used-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-used-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-unused-import', hasModuleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-used-import', hasModuleSideEffects: true } + ] + ); + } + } + } +}; diff --git a/test/function/samples/deprecated/resolve-id/main.js b/test/function/samples/deprecated/resolve-id/main.js new file mode 100644 index 00000000000..dba6f1c07dc --- /dev/null +++ b/test/function/samples/deprecated/resolve-id/main.js @@ -0,0 +1,25 @@ +import 'sideeffects-false-usereffects-false'; +import { value as unusedValue1 } from 'sideeffects-false-usereffects-false-unused-import'; +import { value as usedValue1 } from 'sideeffects-false-usereffects-false-used-import'; + +import 'sideeffects-null-usereffects-false'; +import { value as unusedValue2 } from 'sideeffects-null-usereffects-false-unused-import'; +import { value as usedValue2 } from 'sideeffects-null-usereffects-false-used-import'; + +import 'sideeffects-true-usereffects-false'; +import { value as unusedValue3 } from 'sideeffects-true-usereffects-false-unused-import'; +import { value as usedValue3 } from 'sideeffects-true-usereffects-false-used-import'; + +import 'sideeffects-false-usereffects-true'; +import { value as unusedValue4 } from 'sideeffects-false-usereffects-true-unused-import'; +import { value as usedValue4 } from 'sideeffects-false-usereffects-true-used-import'; + +import 'sideeffects-null-usereffects-true'; +import { value as unusedValue5 } from 'sideeffects-null-usereffects-true-unused-import'; +import { value as usedValue5 } from 'sideeffects-null-usereffects-true-used-import'; + +import 'sideeffects-true-usereffects-true'; +import { value as unusedValue6 } from 'sideeffects-true-usereffects-true-unused-import'; +import { value as usedValue6 } from 'sideeffects-true-usereffects-true-used-import'; + +export const values = [usedValue1, usedValue2, usedValue3, usedValue4, usedValue5, usedValue6]; diff --git a/test/function/samples/deprecations/dynamicImportFunction/main.js b/test/function/samples/deprecations/dynamicImportFunction/main.js index f8a2d88d245..cc1d88a24fa 100644 --- a/test/function/samples/deprecations/dynamicImportFunction/main.js +++ b/test/function/samples/deprecations/dynamicImportFunction/main.js @@ -1,11 +1 @@ -const foo = {}; - -function doIt(x) { - if (foo[x]) { - return true; - } - foo[x] = true; -} - -doIt('x'); -assert.ok(doIt('x'), 'foo was not reassigned'); +assert.ok(true); diff --git a/test/function/samples/deprecations/hasModuleSideEffects/_config.js b/test/function/samples/deprecations/hasModuleSideEffects/_config.js new file mode 100644 index 00000000000..530cdee0072 --- /dev/null +++ b/test/function/samples/deprecations/hasModuleSideEffects/_config.js @@ -0,0 +1,26 @@ +const assert = require('assert'); +const path = require('path'); + +module.exports = { + description: 'warns that accessing "ModuleInfo.hasModuleSideEffects" is deprecated', + options: { + strictDeprecations: true, + plugins: [ + { + name: 'test', + moduleParsed({ hasModuleSideEffects }) { + assert.ok(hasModuleSideEffects); + } + } + ] + }, + error: { + code: 'PLUGIN_ERROR', + hook: 'moduleParsed', + message: + 'Accessing ModuleInfo.hasModuleSideEffects from plugins is deprecated. Please use ModuleInfo.moduleSideEffects instead.', + plugin: 'test', + pluginCode: 'DEPRECATED_FEATURE', + watchFiles: [path.join(__dirname, 'main.js')] + } +}; diff --git a/test/function/samples/deprecations/hasModuleSideEffects/main.js b/test/function/samples/deprecations/hasModuleSideEffects/main.js new file mode 100644 index 00000000000..cc1d88a24fa --- /dev/null +++ b/test/function/samples/deprecations/hasModuleSideEffects/main.js @@ -0,0 +1 @@ +assert.ok(true); diff --git a/test/function/samples/manual-chunks-info/_config.js b/test/function/samples/manual-chunks-info/_config.js index b30f082d6e0..01a590d7ebc 100644 --- a/test/function/samples/manual-chunks-info/_config.js +++ b/test/function/samples/manual-chunks-info/_config.js @@ -111,7 +111,7 @@ module.exports = { dynamicallyImportedIds: [getId('dynamic')], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -146,7 +146,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [getId('dynamic')], hasDefaultExport: null, - hasModuleSideEffects: true, + moduleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -179,7 +179,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: true, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -263,7 +263,7 @@ module.exports = { dynamicallyImportedIds: ['external'], dynamicImporters: [getId('main')], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/function/samples/module-parsed-hook/_config.js b/test/function/samples/module-parsed-hook/_config.js index 75e093d2e4d..507e5d6d180 100644 --- a/test/function/samples/module-parsed-hook/_config.js +++ b/test/function/samples/module-parsed-hook/_config.js @@ -52,7 +52,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -109,7 +109,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/function/samples/module-side-effects/resolve-id-external/_config.js b/test/function/samples/module-side-effects/resolve-id-external/_config.js index 7d612eb749b..fb18613d977 100644 --- a/test/function/samples/module-side-effects/resolve-id-external/_config.js +++ b/test/function/samples/module-side-effects/resolve-id-external/_config.js @@ -49,29 +49,26 @@ module.exports = { Array.from(this.getModuleIds()) .filter(id => !path.isAbsolute(id)) .sort() - .map(id => ({ id, hasModuleSideEffects: this.getModuleInfo(id).hasModuleSideEffects })), + .map(id => ({ id, moduleSideEffects: this.getModuleInfo(id).moduleSideEffects })), [ - { id: 'sideeffects-false-usereffects-false', hasModuleSideEffects: false }, - { - id: 'sideeffects-false-usereffects-false-unused-import', - hasModuleSideEffects: false - }, - { id: 'sideeffects-false-usereffects-false-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true-unused-import', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false-unused-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-true', hasModuleSideEffects: true }, - { id: 'sideeffects-null-usereffects-true-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-null-usereffects-true-used-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false-used-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true-used-import', hasModuleSideEffects: true } + { id: 'sideeffects-false-usereffects-false', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-false-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-false-used-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-used-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-used-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-true', moduleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-used-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-used-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-used-import', moduleSideEffects: true } ] ); } diff --git a/test/function/samples/module-side-effects/resolve-id/_config.js b/test/function/samples/module-side-effects/resolve-id/_config.js index b67cfa57c98..1e76e1f76c5 100644 --- a/test/function/samples/module-side-effects/resolve-id/_config.js +++ b/test/function/samples/module-side-effects/resolve-id/_config.js @@ -46,7 +46,7 @@ module.exports = { const sideEffects = JSON.parse(id.split('-')[1]); const userEffects = JSON.parse(id.split('-')[3]); assert.strictEqual( - this.getModuleInfo(id).hasModuleSideEffects, + this.getModuleInfo(id).moduleSideEffects, typeof sideEffects === 'boolean' ? sideEffects : userEffects ); return `export const value = '${id}'; sideEffects.push(value);`; @@ -57,29 +57,26 @@ module.exports = { Array.from(this.getModuleIds()) .filter(id => !path.isAbsolute(id)) .sort() - .map(id => ({ id, hasModuleSideEffects: this.getModuleInfo(id).hasModuleSideEffects })), + .map(id => ({ id, moduleSideEffects: this.getModuleInfo(id).moduleSideEffects })), [ - { id: 'sideeffects-false-usereffects-false', hasModuleSideEffects: false }, - { - id: 'sideeffects-false-usereffects-false-unused-import', - hasModuleSideEffects: false - }, - { id: 'sideeffects-false-usereffects-false-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true-unused-import', hasModuleSideEffects: false }, - { id: 'sideeffects-false-usereffects-true-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false-unused-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-false-used-import', hasModuleSideEffects: false }, - { id: 'sideeffects-null-usereffects-true', hasModuleSideEffects: true }, - { id: 'sideeffects-null-usereffects-true-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-null-usereffects-true-used-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-false-used-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true-unused-import', hasModuleSideEffects: true }, - { id: 'sideeffects-true-usereffects-true-used-import', hasModuleSideEffects: true } + { id: 'sideeffects-false-usereffects-false', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-false-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-false-used-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-false-usereffects-true-used-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-unused-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-false-used-import', moduleSideEffects: false }, + { id: 'sideeffects-null-usereffects-true', moduleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-null-usereffects-true-used-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-false-used-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-unused-import', moduleSideEffects: true }, + { id: 'sideeffects-true-usereffects-true-used-import', moduleSideEffects: true } ] ); } diff --git a/test/function/samples/module-side-effects/writable/_config.js b/test/function/samples/module-side-effects/writable/_config.js new file mode 100644 index 00000000000..576c4efcc81 --- /dev/null +++ b/test/function/samples/module-side-effects/writable/_config.js @@ -0,0 +1,72 @@ +const assert = require('assert'); +const path = require('assert'); + +// We prefix the polyfill with \0 to tell other plugins not to try to load or +// transform it +const POLYFILL_ID = '\0polyfill'; +const PROXY_SUFFIX = '?inject-polyfill-proxy'; + +module.exports = { + description: 'ModuleInfo.moduleSideEffects should be writable during build time', + options: { + treeshake: { moduleSideEffects: false }, + plugins: [ + { + name: 'inject-polyfill', + async resolveId(source, importer, options) { + if (source === POLYFILL_ID) { + // It is important that side effects are always respected for + // polyfills, otherwise using treeshake.moduleSideEffects: false + // may prevent the polyfill from being included. + return { id: POLYFILL_ID, moduleSideEffects: true }; + } + if (options.isEntry) { + // Determine what the actual entry would have been. We need + // "skipSelf" to avoid an infinite loop. + const resolution = await this.resolve(source, importer, { skipSelf: true, ...options }); + // If it cannot be resolved or is external, just return it so that + // Rollup can display an error + if (!resolution || resolution.external) return resolution; + // In the load hook of the proxy, need to know if the entry has a + // default export. In the load hook, however, we no longer have the + // full "resolution" object that may contain meta-data from other + // plugins that is only added on first load. Therefore we trigger + // loading here. + const moduleInfo = await this.load(resolution); + // We need to make sure side effects in the original entry point + // are respected even for treeshake.moduleSideEffects: false + moduleInfo.moduleSideEffects = true; + // It is important that the new entry does not start with \0 and + // has the same directory as the original one to not mess up + // relative external import generation. Also keeping the name and + // just adding a "?query" to the end ensures that preserveModules + // will generate the original entry name for this entry. + return `${resolution.id}${PROXY_SUFFIX}`; + } + return null; + }, + load(id) { + if (id === POLYFILL_ID) { + return 'global.polyfilled = true;'; + } + if (id.endsWith(PROXY_SUFFIX)) { + const entryId = id.slice(0, -PROXY_SUFFIX.length); + // We know its ModuleInfo is reliable because we awaited this.load + // in resolveId + const { hasDefaultExport } = this.getModuleInfo(entryId); + let code = + `import ${JSON.stringify(POLYFILL_ID)};` + + `export * from ${JSON.stringify(entryId)};`; + // Namespace reexports do not reexport default, so we need special + // handling here + if (hasDefaultExport) { + code += `export { default } from ${JSON.stringify(entryId)};`; + } + return code; + } + return null; + } + } + ] + } +}; diff --git a/test/function/samples/module-side-effects/writable/foo.js b/test/function/samples/module-side-effects/writable/foo.js new file mode 100644 index 00000000000..d02ba545bd3 --- /dev/null +++ b/test/function/samples/module-side-effects/writable/foo.js @@ -0,0 +1 @@ +export default 'foo'; diff --git a/test/function/samples/module-side-effects/writable/main.js b/test/function/samples/module-side-effects/writable/main.js new file mode 100644 index 00000000000..36f47a320b0 --- /dev/null +++ b/test/function/samples/module-side-effects/writable/main.js @@ -0,0 +1,2 @@ +assert.ok(global.polyfilled, 'polyfill working'); +delete global.polyfilled; diff --git a/test/function/samples/plugin-module-information/_config.js b/test/function/samples/plugin-module-information/_config.js index f5ad0b6e12a..eba9ccd654c 100644 --- a/test/function/samples/plugin-module-information/_config.js +++ b/test/function/samples/plugin-module-information/_config.js @@ -21,7 +21,7 @@ module.exports = { hasDefaultExport: null, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], - hasModuleSideEffects: true, + moduleSideEffects: true, id, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -185,7 +185,7 @@ module.exports = { dynamicallyImportedIds: [ID_NESTED, ID_PATH], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -273,7 +273,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_FOO, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -301,7 +301,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [ID_MAIN], hasDefaultExport: null, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_PATH, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -384,7 +384,7 @@ module.exports = { dynamicallyImportedIds: [], dynamicImporters: [ID_MAIN], hasDefaultExport: false, - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_NESTED, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], diff --git a/test/function/samples/preload-module/_config.js b/test/function/samples/preload-module/_config.js index 51ec568846e..f3c8608c1da 100644 --- a/test/function/samples/preload-module/_config.js +++ b/test/function/samples/preload-module/_config.js @@ -36,7 +36,7 @@ module.exports = { hasDefaultExport: false, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], @@ -77,7 +77,7 @@ module.exports = { hasDefaultExport: false, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], - hasModuleSideEffects: true, + moduleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [],