Skip to content

Commit

Permalink
fix: separately export __proto__ for compatibility with CJS Transpi…
Browse files Browse the repository at this point in the history
…ler Re-exports (#5380)

* test: add a basic test

* feat: Export __proto__ outside the forEach

* test: update test snapshots

* Enhance the reliability of the test

* 4.10.0

* Introduce output.reexportProtoFromExternal option

* Update documentation

* Adapt tests

* Tweak documentation

* Update Command line flags

---------

Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
  • Loading branch information
TrickyPi and lukastaegert committed Feb 15, 2024
1 parent 7624208 commit f74d0a9
Show file tree
Hide file tree
Showing 33 changed files with 279 additions and 46 deletions.
1 change: 1 addition & 0 deletions cli/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Basic options:
--preserveModules Preserve module structure
--preserveModulesRoot Put preserved modules under this path at root level
--preserveSymlinks Do not follow symlinks when resolving files
--no-reexportProtoFromExternal Ignore `__proto__` in star re-exports
--no-sanitizeFileName Do not replace invalid characters in file names
--shimMissingExports Create shim variables for missing exports
--silent Don't print warnings
Expand Down
1 change: 1 addition & 0 deletions docs/command-line-interface/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ Many options have command line equivalents. In those cases, any arguments passed
--preserveModules Preserve module structure
--preserveModulesRoot Put preserved modules under this path at root level
--preserveSymlinks Do not follow symlinks when resolving files
--no-reexportProtoFromExternal Ignore `__proto__` in star re-exports
--no-sanitizeFileName Do not replace invalid characters in file names
--shimMissingExports Create shim variables for missing exports
--silent Don't print warnings
Expand Down
45 changes: 45 additions & 0 deletions docs/configuration-options/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,51 @@ export default {
This will generate an additional `noConflict` export to UMD bundles. When called in an IIFE scenario, this method will return the bundle exports while restoring the corresponding global variable to its previous value.
### output.reexportProtoFromExternal
| | |
| --: | :-- |
| Type: | `boolean` |
| CLI: | `--reexportProtoFromExternal`/`--no-reexportProtoFromExternal` |
| Default: | `true` |
This option is only effective when [`output.format`](#output-format) is set to one of `['amd', 'cjs', 'iife', 'umd']` and [`output.externalLiveBindings`](#output-externallivebindings) is set to false.
For maximum compatibility, Rollup reexports `__proto__` from an external module by default. However, for common use cases, it is strongly recommended to set this value to false as it effectively reduces the output size.
```js
// the input file
export * from 'rollup';
```
```js
// the output file if the output.format is cjs
'use strict';

// reexportProtoFromExternal is true
var rollup = require('rollup');

Object.prototype.hasOwnProperty.call(rollup, '__proto__') &&
!Object.prototype.hasOwnProperty.call(exports, '__proto__') &&
Object.defineProperty(exports, '__proto__', {
enumerable: true,
value: rollup['__proto__']
});

Object.keys(rollup).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k))
exports[k] = rollup[k];
});

// reexportProtoFromExternal is false
var rollup = require('rollup');

Object.keys(rollup).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k))
exports[k] = rollup[k];
});
```
### output.sanitizeFileName
| | |
Expand Down
1 change: 1 addition & 0 deletions docs/javascript-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ const outputOptions = {
freeze,
indent,
noConflict,
reexportProtoFromExternal,
sanitizeFileName,
strict,
systemNullSetters,
Expand Down
7 changes: 7 additions & 0 deletions docs/repl/stores/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ export const useOptions = defineStore('options2', () => {
available: optionOutputPreserveModules.value,
name: 'output.preserveModulesRoot'
});
const optionOutputReexportProtoFromExternal = getBoolean({
available: () =>
isInteropFormat.value && optionOutputExternalLiveBindings.value.value === false,
defaultValue: true,
name: 'output.reexportProtoFromExternal'
});
const optionOutputSanitizeFileName = getBoolean({
available: alwaysTrue,
defaultValue: true,
Expand Down Expand Up @@ -450,6 +456,7 @@ export const useOptions = defineStore('options2', () => {
optionOutputPaths,
optionOutputPreserveModules,
optionOutputPreserveModulesRoot,
optionOutputReexportProtoFromExternal,
optionOutputSourcemap,
optionOutputSourcemapFileNames,
optionOutputSanitizeFileName,
Expand Down
4 changes: 3 additions & 1 deletion src/finalisers/amd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function amd(
freeze,
generatedCode: { symbols },
interop,
reexportProtoFromExternal,
strict
}: NormalizedOutputOptions
): void {
Expand Down Expand Up @@ -83,7 +84,8 @@ export default function amd(
interop,
snippets,
t,
externalLiveBindings
externalLiveBindings,
reexportProtoFromExternal
);
let namespaceMarkers = getNamespaceMarkers(
namedExportsMode && hasExports,
Expand Down
2 changes: 2 additions & 0 deletions src/finalisers/cjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function cjs(
freeze,
interop,
generatedCode: { symbols },
reexportProtoFromExternal,
strict
}: NormalizedOutputOptions
): void {
Expand Down Expand Up @@ -66,6 +67,7 @@ export default function cjs(
snippets,
t,
externalLiveBindings,
reexportProtoFromExternal,
`module.exports${_}=${_}`
);

Expand Down
4 changes: 3 additions & 1 deletion src/finalisers/iife.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function iife(
extend,
freeze,
externalLiveBindings,
reexportProtoFromExternal,
globals,
interop,
name,
Expand Down Expand Up @@ -116,7 +117,8 @@ export default function iife(
interop,
snippets,
t,
externalLiveBindings
externalLiveBindings,
reexportProtoFromExternal
);
let namespaceMarkers = getNamespaceMarkers(
namedExportsMode && hasExports,
Expand Down
17 changes: 12 additions & 5 deletions src/finalisers/shared/getExportBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function getExportBlock(
snippets: GenerateCodeSnippets,
t: string,
externalLiveBindings: boolean,
reexportProtoFromExternal: boolean,
mechanism = 'return '
): string {
const { _, getDirectReturnFunction, getFunctionIntro, getPropertyAccess, n, s } = snippets;
Expand Down Expand Up @@ -102,6 +103,16 @@ export function getExportBlock(
for (const specifier of reexports) {
if (specifier.reexported === '*') {
if (exportBlock) exportBlock += n;
if (!specifier.needsLiveBinding && reexportProtoFromExternal) {
const protoString = "'__proto__'";
exportBlock +=
`Object.prototype.hasOwnProperty.call(${name},${_}${protoString})${_}&&${n}` +
`${t}!Object.prototype.hasOwnProperty.call(exports,${_}${protoString})${_}&&${n}` +
`${t}Object.defineProperty(exports,${_}${protoString},${_}{${n}` +
`${t}${t}enumerable:${_}true,${n}` +
`${t}${t}value:${_}${name}[${protoString}]${n}` +
`${t}});${n}${n}`;
}
const copyPropertyIfNecessary = `{${n}${t}if${_}(k${_}!==${_}'default'${_}&&${_}!Object.prototype.hasOwnProperty.call(exports,${_}k))${_}${getDefineProperty(
name,
specifier.needsLiveBinding,
Expand Down Expand Up @@ -251,9 +262,5 @@ const getDefineProperty = (
`${t}${t}get:${_}${left}${name}[k]${right}${n}${t}})`
);
}
return (
`k${_}===${_}'__proto__'${_}?${_}Object.defineProperty(exports,${_}k,${_}{${n}` +
`${t}${t}enumerable:${_}true,${n}` +
`${t}${t}value:${_}${name}[k]${n}${t}})${_}:${_}exports[k]${_}=${_}${name}[k]`
);
return `exports[k]${_}=${_}${name}[k]`;
};
4 changes: 3 additions & 1 deletion src/finalisers/umd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default function umd(
generatedCode: { symbols },
globals,
noConflict,
reexportProtoFromExternal,
strict
}: NormalizedOutputOptions
): void {
Expand Down Expand Up @@ -202,7 +203,8 @@ export default function umd(
interop,
snippets,
t,
externalLiveBindings
externalLiveBindings,
reexportProtoFromExternal
);
let namespaceMarkers = getNamespaceMarkers(
namedExportsMode && hasExports,
Expand Down
2 changes: 2 additions & 0 deletions src/rollup/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ export interface OutputOptions {
plugins?: OutputPluginOption;
preserveModules?: boolean;
preserveModulesRoot?: string;
reexportProtoFromExternal?: boolean;
sanitizeFileName?: boolean | ((fileName: string) => string);
sourcemap?: boolean | 'inline' | 'hidden';
sourcemapBaseUrl?: string;
Expand Down Expand Up @@ -776,6 +777,7 @@ export interface NormalizedOutputOptions {
plugins: OutputPlugin[];
preserveModules: boolean;
preserveModulesRoot: string | undefined;
reexportProtoFromExternal: boolean;
sanitizeFileName: (fileName: string) => string;
sourcemap: boolean | 'inline' | 'hidden';
sourcemapBaseUrl: string | undefined;
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/mergeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ async function mergeOutputOptions(
plugins: await normalizePluginOption(config.plugins),
preserveModules: getOption('preserveModules'),
preserveModulesRoot: getOption('preserveModulesRoot'),
reexportProtoFromExternal: getOption('reexportProtoFromExternal'),
sanitizeFileName: getOption('sanitizeFileName'),
sourcemap: getOption('sourcemap'),
sourcemapBaseUrl: getOption('sourcemapBaseUrl'),
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/normalizeOutputOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export async function normalizeOutputOptions(
plugins: await normalizePluginOption(config.plugins),
preserveModules,
preserveModulesRoot: getPreserveModulesRoot(config),
reexportProtoFromExternal: config.reexportProtoFromExternal ?? true,
sanitizeFileName:
typeof config.sanitizeFileName === 'function'
? config.sanitizeFileName
Expand Down
50 changes: 50 additions & 0 deletions test/form/samples/cjs-transpiler-re-exports-1/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const { execSync } = require('node:child_process');

module.exports = defineTest({
description:
'Disable reexporting the __proto__ from the external module if both output.externalLiveBindings and output.reExportProtoFromExternal are false',
formats: ['cjs'],
options: {
output: {
externalLiveBindings: false,
reexportProtoFromExternal: false
},
plugins: [
{
resolveId(id) {
if (id.endsWith('main.js')) {
return { id };
}
return {
id,
external: true
};
},
buildEnd() {
this.emitFile({
type: 'prebuilt-chunk',
fileName: 'foo.js',
code:
'exports.foo = 1;\n' +
'Object.defineProperty(exports, "__proto__", {\n' +
' enumerable: true,\n' +
' value: 2\n' +
'});'
});
this.emitFile({
type: 'prebuilt-chunk',
fileName: 'execute.mjs',
code:
'import assert from "node:assert";\n' +
'import { foo, __proto__ } from "./cjs.js";\n' +
'assert.strictEqual(foo, 1);\n' +
'assert.strictEqual(__proto__, undefined);'
});
}
}
]
},
after() {
execSync('node _actual/execute.mjs', { cwd: __dirname });
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

var foo_js = require('./foo.js');



Object.keys(foo_js).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = foo_js[k];
});
1 change: 1 addition & 0 deletions test/form/samples/cjs-transpiler-re-exports-1/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './foo.js';
49 changes: 49 additions & 0 deletions test/form/samples/cjs-transpiler-re-exports/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { execSync } = require('node:child_process');

module.exports = defineTest({
description:
'Compatibility with CJS Transpiler Re-exports if output.externalLiveBindings is false',
formats: ['cjs'],
options: {
output: {
externalLiveBindings: false
},
plugins: [
{
resolveId(id) {
if (id.endsWith('main.js')) {
return { id };
}
return {
id,
external: true
};
},
buildEnd() {
this.emitFile({
type: 'prebuilt-chunk',
fileName: 'foo.js',
code:
'exports.foo = 1;\n' +
'Object.defineProperty(exports, "__proto__", {\n' +
' enumerable: true,\n' +
' value: 2\n' +
'});'
});
this.emitFile({
type: 'prebuilt-chunk',
fileName: 'execute.mjs',
code:
'import assert from "node:assert";\n' +
'import { foo, __proto__ } from "./cjs.js";\n' +
'assert.strictEqual(foo, 1);\n' +
'assert.strictEqual(__proto__, 2);'
});
}
}
]
},
after() {
execSync('node _actual/execute.mjs', { cwd: __dirname });
}
});
16 changes: 16 additions & 0 deletions test/form/samples/cjs-transpiler-re-exports/_expected/cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

var foo_js = require('./foo.js');



Object.prototype.hasOwnProperty.call(foo_js, '__proto__') &&
!Object.prototype.hasOwnProperty.call(exports, '__proto__') &&
Object.defineProperty(exports, '__proto__', {
enumerable: true,
value: foo_js['__proto__']
});

Object.keys(foo_js).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = foo_js[k];
});
1 change: 1 addition & 0 deletions test/form/samples/cjs-transpiler-re-exports/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './foo.js';
12 changes: 8 additions & 4 deletions test/form/samples/export-__proto__/_expected/amd.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ define(['exports', 'x'], (function (exports, x$1) { 'use strict';
value: __proto__
});
exports.x = x;
Object.keys(x$1).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) k === '__proto__' ? Object.defineProperty(exports, k, {
Object.prototype.hasOwnProperty.call(x$1, '__proto__') &&
!Object.prototype.hasOwnProperty.call(exports, '__proto__') &&
Object.defineProperty(exports, '__proto__', {
enumerable: true,
value: x$1[k]
}) : exports[k] = x$1[k];
value: x$1['__proto__']
});

Object.keys(x$1).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = x$1[k];
});

}));

0 comments on commit f74d0a9

Please sign in to comment.