Skip to content

Dynamically importing the same file twice in a specific way, incorrectly processes the second request. #18752

@Jimmy89

Description

@Jimmy89

Bug report

  async *firstFunction() {

    const { generateSummary } = await import(
      /* webpackInclude: /[/]selectors[/](?!.*\.test).*\.m?js$/ */
      /* webpackChunkName: "store-selectors" */
      /* webpackMode: "lazy-once" */
      `./selectors/${this.type}.mjs`
    );
   console.log(typeof generateSummary) // = "function"
  }

  async *secondFunction() {

    const {entityActionQueue} = await import(
      /* webpackInclude: /[/]selectors[/](?!.*\.test).*\.m?js$/ */
      /* webpackChunkName: "store-selectors" */
      /* webpackMode: "lazy-once" */
      `./selectors/${this.type}.mjs`
    );
   console.log(entityActionQueue)  // =undefined

    const logFunctions = await import(
      /* webpackInclude: /[/]selectors[/](?!.*\.test).*\.m?js$/ */
      /* webpackChunkName: "store-selectors" */
      /* webpackMode: "lazy-once" */
      `./selectors/${this.type}.mjs`
    );
   console.log(logFunctions)  // = { generateSummary: function, [randomLetter]: function } // <-- the randomLetter turns out to be the entityActionQueue function, but is mangled.
}

If I have two functions (within the same file) dynamically importing another file (also same file), but a different function for that file, I get as result that the second function name is mangled. See the code above.

However, if I import the full module and use the magic comment webpackExports, the second function name remains, as expected. See working example below:

  async *firstFunction() {

    const selectorModule = await import(
      /* webpackInclude: /[/]selectors[/](?!.*\.test).*\.m?js$/ */
      /* webpackChunkName: "store-selectors" */
      /* webpackMode: "lazy-once" */
      /* webpackExports: ["generateSummary", "entityActionQueue"] */
      `./selectors/${this.type}.mjs`
    );
   console.log(typeof selectorModule.generateSummary) // = "function"
  }

  async *secondFunction() {

    const selectorModule = await import(
      /* webpackInclude: /[/]selectors[/](?!.*\.test).*\.m?js$/ */
      /* webpackChunkName: "store-selectors" */
      /* webpackMode: "lazy-once" */
      /* webpackExports: ["generateSummary", "entityActionQueue"] */
      `./selectors/${this.type}.mjs`
    );
   console.log(typeof selectorModule.entityActionQueue)  // = "function"
}

What is the current behavior?

As described as comment in the code above, only through webpackExports the exported functions can be specified (and function correctly). I would expect webpack to recognize that multiple exports from the same file are being requested.

If the current behavior is a bug, please provide the steps to reproduce.

I wouldn't be surprised in the webpack config I'm using is needed for reproduction (I have set 'modern' settings).

import path from "node:path";
import { fileURLToPath } from "url";
import TerserPlugin from "terser-webpack-plugin";
import fs from "node:fs";
import webpack from "webpack";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const releaseDir = {
  "lambda": path.resolve(__dirname, "dist/lambda"),
};

const globalConfig = {
  mode: "production",
  experiments: {
    outputModule: true,
    futureDefaults: true,
    asyncWebAssembly: true,
    topLevelAwait: true,
    layers: true,
  },
  context: __dirname,
  output: {
    filename: 'index.js',
    clean: true,
    module: true,
    scriptType: "module",
    chunkFormat: "module",
    enabledLibraryTypes: ['module'],
    ignoreBrowserWarnings: true,
    environment: {
      module: true,
      nodePrefixForCoreModules: true,
      dynamicImport: true,
      bigIntLiteral: true,
    },
    library: {
      type: "module",
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': "process.env",
    })
  ],
  devtool: "source-map",
  cache: {
    type: "filesystem",
    compression: 'gzip',
    hashAlgorithm: 'sha1',
    cacheDirectory: path.resolve(__dirname, './node_modules/.cache/webpack'),
  },
  optimization: {
    checkWasmTypes: false,
    removeAvailableModules: true,
    usedExports: true,
    minimize: false, // IMPORTANT: this is set to false, I have the issue with this setting set to both false as true.
    realContentHash: true, // Should always be set in production mode, enforcing it.
    minimizer: [
      {
        apply: (compiler) => {
          new TerserPlugin({
            extractComments: false,
            terserOptions: {
              parse: {},
              compress: {
                defaults: true,
                passes: 4, // Adjusted
                ecma: 2024, // Adjusted
                module: true,// Adjusted
              },
              mangle: true,
              module: true, // Adjusted
              ecma: 2024, // Adjusted
              output: null,
              format: null,
              toplevel: false,
              nameCache: null,
              ie8: false,
              keep_classnames: true, // Adjusted, we like our class names
              keep_fnames: false,
              safari10: false,
            },
          }).apply(compiler);
        },
      },
    ],
  },
  externalsType: 'import',
  externalsPresets: {
    node: true
  },
};


const configurations = (env) => {
  return [
    {
      ...globalConfig,
      name: "lambda",
      target: "node20",
      entry: {
        lambda: path.resolve(__dirname, "./index.mjs"),
      },
      cache: {
        ...globalConfig.cache,
        memoryCacheUnaffected: true,
      },
     output: {
        ...globalConfig.output,
        path: releaseDir.lambda,
        filename: "[name].mjs",
      },
      optimization: {
        ...globalConfig.optimization,
        runtimeChunk: "single",
        moduleIds: 'deterministic', // Default in production mode, enforcing it
        splitChunks: {
          cacheGroups: {
            // Create one chunk for all node_modules packages
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
              chunks: 'all',
            },
          },
        },
      }
    }
  ];
}

export default configurations;

What is the expected behavior?

That with the first example code the dynamically imports are recognized by webpack and both functions are properly exported (no function name changes), in this case: { generateSummary: function, entityActionQueue: function }

Other relevant information:
webpack version: 5.94.0
Node.js version: NodeJS 20.16
Operating System: Fedora
Additional tools:

  • webpack-cli: 5.1.4

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions