Skip to content

Exported "namespaced imports" are lost when treeshake is false #4174

@milesj

Description

@milesj

Rollup Version

2.53.0

Operating System (or Browser)

Linux, macOS

Node Version (if applicable)

10+

Link To Reproduction

https://github.com/milesj/rollup-preserve-namespaces

PR I encountered it: milesj/boost#151

Expected Behaviour

It looks like namespace imports that are not used but are exported are dropped, for example this fails:

export * as ns from './namespace';

However, namespace imports that are used within the same file that are exported persist, for example this passes:

import * as ns from './namespace';

export { ns };
export const test = { ...ns };

Discovery

I started updating a project of mine to the latest Rollup version, and immediately noticed that a ton of unit tests are failing with the following errors (as seen in the REPL/PR link):

TypeError: Cannot read property 'parse' of undefined

      14 |
      15 |       resp.on('end', () => {
    > 16 |         const pkg = json.parse<{ 'dist-tags': Record<string, string> }>(data);
TypeError: Cannot read property 'console' of undefined

      114 |       transports: [
      115 |         new StreamTransport({
    > 116 |           format: formats.console,

This is very weird, as this code hasn't changed in a long time and is pretty straight forward. After digging into it further, it looks like namespaced imports that are exported are completely dropped and are no longer exported in the transpiled output. For example, this code:

/**
 * @copyright   2020, Miles Johnson
 * @license     https://opensource.org/licenses/MIT
 */

import optimal, { Blueprint, Predicates, predicates } from 'optimal';
import type { CommonErrorCode } from './CommonError';
import CommonError from './CommonError';
import Contract from './Contract';
import ExitError from './ExitError';
import PackageGraph from './PackageGraph';
import Path from './Path';
import PathResolver from './PathResolver';
import Project from './Project';

export * from './constants';
export * from './helpers';
export * from './types';
export * from '@boost/decorators';

// THESE 2 EXPORTS
export * as json from './serializers/json';
export * as yaml from './serializers/yaml';

export {
  CommonError,
  Contract,
  ExitError,
  optimal,
  PackageGraph,
  Path,
  PathResolver,
  predicates,
  Project,
};

export type { Blueprint, CommonErrorCode, Predicates };

Is transpiled to the following. Notice that json and yaml exports do not exist! The files are required, but they do nothing.

'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var optimal = require('optimal');

var CommonError = require('./CommonError.js');

var Contract = require('./Contract.js');

var ExitError = require('./ExitError.js');

var PackageGraph = require('./PackageGraph.js');

var Path = require('./Path.js');

var PathResolver = require('./PathResolver.js');

var Project = require('./Project.js');

var constants = require('./constants.js');

require('./helpers/index.js');

require('./serializers/json.js');

require('./serializers/yaml.js');

var types = require('./types.js');

var decorators = require('@boost/decorators');

var createBlueprint = require('./helpers/createBlueprint.js');

var deepFreeze = require('./helpers/deepFreeze.js');

var deepMerge = require('./helpers/deepMerge.js');

var formatMs = require('./helpers/formatMs.js');

var instanceOf = require('./helpers/instanceOf.js');

var isEmpty = require('./helpers/isEmpty.js');

var isFilePath = require('./helpers/isFilePath.js');

var isModuleName = require('./helpers/isModuleName.js');

var isObject = require('./helpers/isObject.js');

var isPlainObject = require('./helpers/isPlainObject.js');

var parseFile = require('./helpers/parseFile.js');

var requireModule = require('./helpers/requireModule.js');

var requireTypedModule = require('./helpers/requireTypedModule.js');

var toArray = require('./helpers/toArray.js');

function _interopDefaultLegacy(e) {
  return e && typeof e === 'object' && 'default' in e ? e : {
    'default': e
  };
}

var optimal__default = /*#__PURE__*/_interopDefaultLegacy(optimal);
/**
 * @copyright   2020, Miles Johnson
 * @license     https://opensource.org/licenses/MIT
 */


Object.defineProperty(exports, 'optimal', {
  enumerable: true,
  get: function () {
    return optimal__default['default'];
  }
});
Object.defineProperty(exports, 'predicates', {
  enumerable: true,
  get: function () {
    return optimal.predicates;
  }
});
exports.CommonError = CommonError;
exports.Contract = Contract;
exports.ExitError = ExitError;
exports.PackageGraph = PackageGraph;
exports.Path = Path;
exports.PathResolver = PathResolver;
exports.Project = Project;
exports.MODULE_NAME_PART = constants.MODULE_NAME_PART;
exports.MODULE_NAME_PATTERN = constants.MODULE_NAME_PATTERN;
Object.defineProperty(exports, 'LookupType', {
  enumerable: true,
  get: function () {
    return types.LookupType;
  }
});
exports.createBlueprint = createBlueprint;
exports.deepFreeze = deepFreeze;
exports.deepMerge = deepMerge;
exports.formatMs = formatMs;
exports.instanceOf = instanceOf;
exports.isEmpty = isEmpty;
exports.isFilePath = isFilePath;
exports.isModuleName = isModuleName;
exports.isObject = isObject;
exports.isPlainObject = isPlainObject;
exports.parseFile = parseFile;
exports.requireModule = requireModule;
exports.requireTypedModule = requireTypedModule;
exports.toArray = toArray;
Object.keys(decorators).forEach(function (k) {
  if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
    enumerable: true,
    get: function () {
      return decorators[k];
    }
  });
});
//# sourceMappingURL=index.js.map

As for the other error above, it is also a namespaced import being exported (https://github.com/milesj/boost/blob/master/packages/log/src/index.ts#L7)

import * as formats from './formats';

export { formats };

Attempts

I've tried different combinations of interop, exports, treeshake, sourceMaps, and any option I could think of. None of them seemed to resolve this issue.

HOWEVER, when I set preserveModules to false and use a single entry point, the export DOES correctly exist.

Instead of export * as X, I also tried the following. It has the same problem (https://github.com/milesj/boost/blob/master/packages/common/src/index.ts#L15).

import * as json from './serializers/json';
import * as yaml from './serializers/yaml';

export { json, yaml };

Config

The configuration is generated by Packemon, which can be found here: https://github.com/milesj/packemon/blob/master/src/rollup/config.ts

Since it's kind of abstracted away, here's a console log of the config while running the build.

{
  cache: undefined,
  external: [Function (anonymous)],
  input: [
    '/Users/milesj/Projects/boost/packages/common/src/CommonError.ts',
    '/Users/milesj/Projects/boost/packages/common/src/Contract.ts',
    '/Users/milesj/Projects/boost/packages/common/src/ExitError.ts',
    '/Users/milesj/Projects/boost/packages/common/src/PackageGraph.ts',
    '/Users/milesj/Projects/boost/packages/common/src/Path.ts',
    '/Users/milesj/Projects/boost/packages/common/src/PathResolver.ts',
    '/Users/milesj/Projects/boost/packages/common/src/Project.ts',
    '/Users/milesj/Projects/boost/packages/common/src/constants.ts',
    '/Users/milesj/Projects/boost/packages/common/src/index.ts',
    '/Users/milesj/Projects/boost/packages/common/src/types.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/createBlueprint.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/deepFreeze.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/deepMerge.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/formatMs.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/index.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/instanceOf.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/isEmpty.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/isFilePath.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/isModuleName.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/isObject.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/isPlainObject.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/parseFile.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/requireModule.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/requireTypedModule.ts',
    '/Users/milesj/Projects/boost/packages/common/src/helpers/toArray.ts',
    '/Users/milesj/Projects/boost/packages/common/src/internal/interopRequireModule.ts',
    '/Users/milesj/Projects/boost/packages/common/src/serializers/json.ts',
    '/Users/milesj/Projects/boost/packages/common/src/serializers/yaml.ts'
  ],
  output: [
    {
      dir: '/Users/milesj/Projects/boost/packages/common/lib',
      format: 'cjs',
      originalFormat: 'lib',
      paths: {},
      assetFileNames: '../assets/[name]-[hash][extname]',
      chunkFileNames: '[name]-[hash].js',
      entryFileNames: '[name].js',
      preserveModules: true,
      preferConst: false,
      plugins: [
        {
          name: 'babel',
          renderStart: [Function: renderStart],
          renderChunk: [Function: renderChunk]
        },
        {
          name: 'packemon-add-bin-shebang',
          generateBundle: [Function: generateBundle]
        }
      ],
      sourcemap: true,
      sourcemapExcludeSources: true,
      exports: 'auto'
    }
  ],
  plugins: [
    {
      name: 'node-externals',
      resolveId: [Function: resolveId],
      buildStart: [Function: buildStart]
    },
    {
      name: 'node-resolve',
      buildStart: [Function: buildStart],
      generateBundle: [Function: generateBundle],
      resolveId: [AsyncFunction: resolveId],
      load: [Function: load],
      getPackageInfoForId: [Function: getPackageInfoForId]
    },
    {
      name: 'commonjs',
      buildStart: [Function: buildStart],
      resolveId: [Function: resolveId],
      load: [Function: load],
      transform: [Function: transform],
      moduleParsed: [Function: moduleParsed]
    },
    { name: 'json', transform: [Function: transform] },
    {
      name: 'babel',
      options: [Function: options],
      resolveId: [Function: resolveId],
      load: [Function: load],
      transform: [Function: transform]
    }
  ],
  treeshake: false
}

Actual Behaviour

Namespaced imports are exported correctly.

It seems to work in the REPL (but cant control preserve modules), so not sure what's happening here: https://rollupjs.org/repl/?version=2.53.0&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmV4cG9ydCUyMColMjBhcyUyMG90aGVyJTIwZnJvbSUyMCcuJTJGb3RoZXIuanMnJTNCJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIyb3RoZXIuanMlMjIlMkMlMjJjb2RlJTIyJTNBJTIyZXhwb3J0JTIwZnVuY3Rpb24lMjBmb28oKSUyMCU3QiU3RCU1Q25leHBvcnQlMjBmdW5jdGlvbiUyMGJhcigpJTIwJTdCJTdEJTIyJTdEJTVEJTJDJTIyb3B0aW9ucyUyMiUzQSU3QiUyMmZvcm1hdCUyMiUzQSUyMmNqcyUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions