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

Make namespace @@toStringTag "Module" non-enumerable #4378

Merged
merged 13 commits into from Mar 2, 2022
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
64 changes: 46 additions & 18 deletions docs/999-big-list-of-options.md
Expand Up @@ -460,7 +460,7 @@ Whether to extend the global variable defined by the `name` option in `umd` or `

#### output.generatedCode

Type: `"es5" | "es2015" | { arrowFunctions?: boolean, constBindings?: boolean, objectShorthand?: boolean, preset?: "es5" | "es2015", reservedNamesAsProps?: boolean }`<br> CLI: `--generatedCode <preset>`<br> Default: `"es5"`
Type: `"es5" | "es2015" | { arrowFunctions?: boolean, constBindings?: boolean, objectShorthand?: boolean, preset?: "es5" | "es2015", reservedNamesAsProps?: boolean, symbols?: boolean }`<br> CLI: `--generatedCode <preset>`<br> Default: `"es5"`

Which language features Rollup can safely use in generated code. This will not transpile any user code but only change the code Rollup uses in wrappers and helpers. You may choose one of several presets:

Expand Down Expand Up @@ -577,6 +577,34 @@ const foo = null;
exports.void = foo;
```

**output.generatedCode.symbols**<br> Type: `boolean`<br> CLI: `--generatedCode.symbols`/`--no-generatedCode.symbols`<br> Default: `false`

Whether to allow the use of `Symbol` in auto-generated code snippets. Currently, this only controls if namespaces will have the `Symbol.toStringTag` property set to the correct value of `Module`, which means that for a namespace, `String(namespace)` logs `[object Module]`. This again is used for feature detection in certain libraries and frameworks.

```javascript
// input
export const foo = 42;

// cjs output with symbols: false
Object.defineProperty(exports, '__esModule', { value: true });

const foo = 42;

exports.foo = foo;

// cjs output with symbols: true
Object.defineProperties(exports, {
__esModule: { value: true },
[Symbol.toStringTag]: { value: 'Module' }
});

const foo = 42;

exports.foo = foo;
```

Note: The `__esModule` flag in the example can be prevented via the [`output.esModule`](https://rollupjs.org/guide/en/#outputesmodule) option.

#### output.hoistTransitiveImports

Type: `boolean`<br> CLI: `--hoistTransitiveImports`/`--no-hoistTransitiveImports`<br> Default: `true`
Expand Down Expand Up @@ -1417,19 +1445,6 @@ export default {
};
```

#### output.namespaceToStringTag

Type: `boolean`<br> CLI: `--namespaceToStringTag`/`--no-namespaceToStringTag`<br> Default: `false`

Whether to add spec compliant `.toString()` tags to namespace objects. If this option is set,

```javascript
import * as namespace from './file.js';
console.log(String(namespace));
```

will always log `[object Module]`;

#### output.noConflict

Type: `boolean`<br> CLI: `--noConflict`/`--no-noConflict`<br> Default: `false`
Expand Down Expand Up @@ -1848,10 +1863,6 @@ _Use the [`output.inlineDynamicImports`](guide/en/#outputinlinedynamicimports) o

_Use the [`output.manualChunks`](guide/en/#outputmanualchunks) output option instead, which has the same signature._

#### preserveModules

_Use the [`output.preserveModules`](guide/en/#outputpreservemodules) output option instead, which has the same signature._

#### output.dynamicImportFunction

_Use the [`renderDynamicImport`](guide/en/#renderdynamicimport) plugin hook instead._<br> Type: `string`<br> CLI: `--dynamicImportFunction <name>`<br> Default: `import`
Expand Down Expand Up @@ -1890,3 +1901,20 @@ console.log(42);
```

You can also supply a list of external ids to be considered pure or a function that is called whenever an external import could be removed.

#### output.namespaceToStringTag

_Use [`output.generatedCode.symbols`](guide/en/#outputgeneratedcode) instead._<br> Type: `boolean`<br> CLI: `--namespaceToStringTag`/`--no-namespaceToStringTag`<br> Default: `false`

Whether to add spec compliant `.toString()` tags to namespace objects. If this option is set,

```javascript
import * as namespace from './file.js';
console.log(String(namespace));
```

will always log `[object Module]`;

#### preserveModules

_Use the [`output.preserveModules`](guide/en/#outputpreservemodules) output option instead, which has the same signature._
22 changes: 12 additions & 10 deletions src/ast/variables/NamespaceVariable.ts
@@ -1,6 +1,6 @@
import type Module from '../../Module';
import type { AstContext } from '../../Module';
import { MERGE_NAMESPACES_VARIABLE } from '../../utils/interopHelpers';
import { getToStringTagValue, MERGE_NAMESPACES_VARIABLE } from '../../utils/interopHelpers';
import type { RenderOptions } from '../../utils/renderHelpers';
import { getSystemExportStatement } from '../../utils/systemJsRendering';
import type Identifier from '../nodes/Identifier';
Expand Down Expand Up @@ -77,24 +77,26 @@ export default class NamespaceVariable extends Variable {
return [name, original.getName(getPropertyAccess)];
}
);

if (namespaceToStringTag) {
members.unshift([null, `[Symbol.toStringTag]:${_}'Module'`]);
}

members.unshift([null, `__proto__:${_}null`]);

let output = getObject(members, { lineBreakIndent: { base: '', t } });
if (this.mergedNamespaces.length > 0) {
const assignmentArgs = this.mergedNamespaces.map(variable =>
variable.getName(getPropertyAccess)
);
output = `/*#__PURE__*/${MERGE_NAMESPACES_VARIABLE}(${output}, [${assignmentArgs.join(
output = `/*#__PURE__*/${MERGE_NAMESPACES_VARIABLE}(${output},${_}[${assignmentArgs.join(
`,${_}`
)}])`;
}
if (freeze) {
output = `/*#__PURE__*/Object.freeze(${output})`;
} else {
// The helper to merge namespaces will also take care of freezing and toStringTag
if (namespaceToStringTag) {
output = `/*#__PURE__*/Object.defineProperty(${output},${_}Symbol.toStringTag,${_}${getToStringTagValue(
getObject
)})`;
}
if (freeze) {
output = `/*#__PURE__*/Object.freeze(${output})`;
}
}

const name = this.getName(getPropertyAccess);
Expand Down
3 changes: 1 addition & 2 deletions src/finalisers/amd.ts
Expand Up @@ -86,8 +86,7 @@ export default function amd(
namedExportsMode && hasExports,
isEntryFacade && esModule,
isModuleFacade && namespaceToStringTag,
_,
n
snippets
);
if (namespaceMarkers) {
namespaceMarkers = n + n + namespaceMarkers;
Expand Down
3 changes: 1 addition & 2 deletions src/finalisers/cjs.ts
Expand Up @@ -38,8 +38,7 @@ export default function cjs(
namedExportsMode && hasExports,
isEntryFacade && esModule,
isModuleFacade && namespaceToStringTag,
_,
n
snippets
);
if (namespaceMarkers) {
namespaceMarkers += n + n;
Expand Down
3 changes: 1 addition & 2 deletions src/finalisers/iife.ts
Expand Up @@ -122,8 +122,7 @@ export default function iife(
namedExportsMode && hasExports,
esModule,
namespaceToStringTag,
_,
n
snippets
);
if (namespaceMarkers) {
namespaceMarkers = n + n + namespaceMarkers;
Expand Down
37 changes: 22 additions & 15 deletions src/finalisers/shared/getExportBlock.ts
Expand Up @@ -3,6 +3,7 @@ import type { GetInterop } from '../../rollup/types';
import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets';
import {
defaultInteropHelpersByInteropType,
getToStringTagValue,
isDefaultAProperty,
namespaceInteropHelpersByInteropType
} from '../../utils/interopHelpers';
Expand Down Expand Up @@ -189,34 +190,40 @@ function getReexportedImportName(
return `${moduleVariableName}${getPropertyAccess(imported)}`;
}

function getEsModuleExport(_: string): string {
return `Object.defineProperty(exports,${_}'__esModule',${_}{${_}value:${_}true${_}});`;
}

function getNamespaceToStringExport(_: string): string {
return `exports[Symbol.toStringTag]${_}=${_}'Module';`;
function getEsModuleValue(getObject: GenerateCodeSnippets['getObject']) {
return getObject([['value', 'true']], {
lineBreakIndent: null
});
}

export function getNamespaceMarkers(
hasNamedExports: boolean,
addEsModule: boolean,
addNamespaceToStringTag: boolean,
_: string,
n: string
{ _, getObject }: GenerateCodeSnippets
): string {
let namespaceMarkers = '';
if (hasNamedExports) {
if (addEsModule) {
namespaceMarkers += getEsModuleExport(_);
if (addNamespaceToStringTag) {
return `Object.defineProperties(exports,${_}${getObject(
[
['__esModule', getEsModuleValue(getObject)],
[null, `[Symbol.toStringTag]:${_}${getToStringTagValue(getObject)}`]
],
{
lineBreakIndent: null
}
)});`;
}
return `Object.defineProperty(exports,${_}'__esModule',${_}${getEsModuleValue(getObject)});`;
}
if (addNamespaceToStringTag) {
if (namespaceMarkers) {
namespaceMarkers += n;
}
namespaceMarkers += getNamespaceToStringExport(_);
return `Object.defineProperty(exports,${_}Symbol.toStringTag,${_}${getToStringTagValue(
getObject
)});`;
}
}
return namespaceMarkers;
return '';
}

const getDefineProperty = (
Expand Down
3 changes: 1 addition & 2 deletions src/finalisers/umd.ts
Expand Up @@ -203,8 +203,7 @@ export default function umd(
namedExportsMode && hasExports,
esModule,
namespaceToStringTag,
_,
n
snippets
);
if (namespaceMarkers) {
namespaceMarkers = n + n + namespaceMarkers;
Expand Down
4 changes: 3 additions & 1 deletion src/rollup/types.d.ts
Expand Up @@ -608,6 +608,7 @@ interface NormalizedGeneratedCodeOptions {
constBindings: boolean;
objectShorthand: boolean;
reservedNamesAsProps: boolean;
symbols: boolean;
}

interface GeneratedCodeOptions extends Partial<NormalizedGeneratedCodeOptions> {
Expand Down Expand Up @@ -681,12 +682,13 @@ export interface OutputOptions {
manualChunks?: ManualChunksOption;
minifyInternalExports?: boolean;
name?: string;
/** @deprecated Use "generatedCode.symbols" instead. */
namespaceToStringTag?: boolean;
noConflict?: boolean;
outro?: string | (() => string | Promise<string>);
paths?: OptionsPaths;
plugins?: (OutputPlugin | null | false | undefined)[];
/** @deprecated Use the "generatedCode.constBindings" instead. */
/** @deprecated Use "generatedCode.constBindings" instead. */
preferConst?: boolean;
preserveModules?: boolean;
preserveModulesRoot?: string;
Expand Down
64 changes: 43 additions & 21 deletions src/utils/interopHelpers.ts
Expand Up @@ -105,28 +105,30 @@ const HELPER_GENERATORS: {
},
[INTEROP_NAMESPACE_DEFAULT_ONLY_VARIABLE](
_t,
{ _, getDirectReturnFunction, getObject, n },
snippets,
_liveBindings: boolean,
freeze: boolean,
namespaceToStringTag: boolean
) {
const { getDirectReturnFunction, getObject, n } = snippets;
const [left, right] = getDirectReturnFunction(['e'], {
functionReturn: true,
lineBreakIndent: null,
name: INTEROP_NAMESPACE_DEFAULT_ONLY_VARIABLE
});
return `${left}${getFrozen(
getObject(
[
['__proto__', 'null'],
...(namespaceToStringTag
? [[null, `[Symbol.toStringTag]:${_}'Module'`] as [null, string]]
: []),
['default', 'e']
],
{ lineBreakIndent: null }
),
freeze
freeze,
getWithToStringTag(
namespaceToStringTag,
getObject(
[
['__proto__', 'null'],
['default', 'e']
],
{ lineBreakIndent: null }
),
snippets
)
)}${right}${n}${n}`;
},
[INTEROP_NAMESPACE_DEFAULT_VARIABLE](t, snippets, liveBindings, freeze, namespaceToStringTag) {
Expand Down Expand Up @@ -161,7 +163,7 @@ const HELPER_GENERATORS: {
`}${n}${n}`
);
},
[MERGE_NAMESPACES_VARIABLE](t, snippets, liveBindings, freeze) {
[MERGE_NAMESPACES_VARIABLE](t, snippets, liveBindings, freeze, namespaceToStringTag) {
const { _, cnst, n } = snippets;
const useForEach = cnst === 'var' && liveBindings;
return (
Expand All @@ -180,7 +182,10 @@ const HELPER_GENERATORS: {
t,
snippets
)}${n}` +
`${t}return ${getFrozen('n', freeze)};${n}` +
`${t}return ${getFrozen(
freeze,
getWithToStringTag(namespaceToStringTag, 'n', snippets)
)};${n}` +
`}${n}${n}`
);
}
Expand All @@ -200,7 +205,7 @@ const createNamespaceObject = (
freeze: boolean,
namespaceToStringTag: boolean
) => {
const { _, cnst, getPropertyAccess, n, s } = snippets;
const { _, cnst, getObject, getPropertyAccess, n, s } = snippets;
const copyProperty =
`{${n}` +
(liveBindings ? copyNonDefaultOwnPropertyLiveBinding : copyPropertyStatic)(
Expand All @@ -210,16 +215,16 @@ const createNamespaceObject = (
) +
`${i}${t}}`;
return (
`${i}${cnst} n${_}=${_}${
`${i}${cnst} n${_}=${_}Object.create(null${
namespaceToStringTag
? `{__proto__:${_}null,${_}[Symbol.toStringTag]:${_}'Module'}`
: 'Object.create(null)'
};${n}` +
? `,${_}{${_}[Symbol.toStringTag]:${_}${getToStringTagValue(getObject)}${_}}`
: ''
});${n}` +
`${i}if${_}(e)${_}{${n}` +
`${i}${t}${loopOverKeys(copyProperty, !liveBindings, snippets)}${n}` +
`${i}}${n}` +
`${i}n${getPropertyAccess('default')}${_}=${_}e;${n}` +
`${i}return ${getFrozen('n', freeze)}${s}${n}`
`${i}return ${getFrozen(freeze, 'n')}${s}${n}`
);
};

Expand Down Expand Up @@ -321,7 +326,24 @@ const copyPropertyLiveBinding = (
const copyPropertyStatic = (_t: string, i: string, { _, n }: GenerateCodeSnippets) =>
`${i}n[k]${_}=${_}e[k];${n}`;

const getFrozen = (fragment: string, freeze: boolean) =>
const getFrozen = (freeze: boolean, fragment: string) =>
freeze ? `Object.freeze(${fragment})` : fragment;

const getWithToStringTag = (
namespaceToStringTag: boolean,
fragment: string,
{ _, getObject }: GenerateCodeSnippets
) =>
namespaceToStringTag
? `Object.defineProperty(${fragment},${_}Symbol.toStringTag,${_}${getToStringTagValue(
getObject
)})`
: fragment;

export const HELPER_NAMES = Object.keys(HELPER_GENERATORS);

export function getToStringTagValue(getObject: GenerateCodeSnippets['getObject']) {
return getObject([['value', "'Module'"]], {
lineBreakIndent: null
});
}