Skip to content

Inlining a relocated asset path into a shorthand object property emits invalid syntax ("Module parse failed") #209

Description

@iwahbe

When a variable holding a statically-known asset path (from require.resolve) is read as a shorthand object property, the relocation overwrites the identifier — which is simultaneously the property's key and value — with the asset expression, producing a bare expression inside an object literal. The result is syntactically invalid and webpack fails with Module parse failed: Unexpected token.

Claude's understanding of the problem & a suggested fix

Mechanism

In src/asset-relocator.js (as of fa39a77):

  1. The require.resolve('./tool.js') assignment registers toolPath as a known absolute-path binding.
  2. At the shorthand property read, isIdentifierRead returns true via its Property case (line ~44: return node === parent.value) — in a shorthand property, acorn makes the key and value the same node, so the check can't distinguish this from a plain value read.
  3. emitStaticChildAsset then runs magicString.overwrite(staticChildNode.start, staticChildNode.end, inlineString) (line ~1318), replacing the identifier — key included — with the bare asset expression.

Possible fixes

Two contained options:

  • In isIdentifierRead, exclude shorthand properties:

    case 'Property':
      return node === parent.value && !parent.shorthand;

    I tested this variant against the loader source: the minimal repro above compiles and runs correctly in both branches (the default branch resolves to the relocated asset because the require.resolve call is still rewritten at the assignment site, and the asset is still emitted), and require("@npmcli/run-script") (v10.0.4) goes from the parse error to compiling and loading successfully, with node-gyp.js emitted. I have not run the repo's unit-test fixtures against it.

  • Alternatively, in emitStaticChildAsset, when staticChildNode is the value of a shorthand Property, emit name: <inlineString> instead of the bare expression (preserves the read-site inlining).

Repro files

other.js:

#!/usr/bin/env node
console.log("gyp stand-in");

index.js:

let toolPath
if (process.env.TOOL_PATH) {
  toolPath = process.env.TOOL_PATH
} else {
  toolPath = require.resolve('./tool.js')
}

console.log({
  name: 'demo',
  toolPath,
})

webpack.config.js:

module.exports = {
  mode: 'production',
  target: 'node',
  entry: './index.js',
  output: { libraryTarget: 'commonjs2' },
  module: {
    rules: [
      {
        test: /\.(m?js|node)$/,
        parser: { amd: false },
        use: { loader: '@vercel/webpack-asset-relocator-loader' },
      },
    ],
  },
};

package.json

{
  "name": "relocator-shorthand-repro",
  "private": true,
  "devDependencies": {
    "@vercel/webpack-asset-relocator-loader": "1.10.0",
    "webpack": "5.108.1",
    "webpack-cli": "^7.1.0"
  },
  "dependencies": {
    "@npmcli/run-script": "10.0.4"
  }
}

Build with @vercel/webpack-asset-relocator-loader@1.10.0 and webpack 5.108.1:

$ npm install && npx webpack
ERROR in ./index.js 10:21
Module parse failed: Unexpected token (10:21)
File was processed with these loaders:
 * ./node_modules/@vercel/webpack-asset-relocator-loader/src/asset-relocator.js
You may need an additional loader to handle the result of these loaders.
| console.log({
|   name: 'demo',
>   __webpack_require__.ab + "tool.js",
| })

Also reproduced through ncc 0.44.1 (which pins this loader at 1.7.3 as its relocate-loader), so the bug spans at least 1.7.3 → 1.10.0 → current main.

Control: changing the shorthand toolPath, to toolPath: toolPath, builds successfully and emits the expected

 console.log({
   name: 'demo',
-  toolPath,
+  toolPath: toolPath,
 })
toolPath: __webpack_require__.ab + "tool.js",

with tool.js relocated next to the bundle and the program running correctly.

Impact

This was run down by Claude when trying to figure out why pulumi/actions#1460 couldn't merge as is.

This is the root cause of the long-standing inability to bundle @npmcli/run-script v9+/pacote v11+/@npmcli/arborist v8+ with ncc (vercel/ncc#986, vercel/ncc#1162 — the latter was closed as unfixable in ncc, but the defect is in this loader and looks contained).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions