web-infra-dev / rspack

The fast Rust-based web bundler with webpack-compatible API 🦀️
https://rspack.dev
MIT License
9.09k stars 521 forks source link

[Feature]: access to `compilation.assetsInfo` in `compilation.hooks.processAssets.tap` #7033

Open iusesoftware opened 2 months ago

iusesoftware commented 2 months ago

What problem does this feature solve?

Hi! I was trying to migrate my webpack v5 setup with several custom-built webpack plugins to Rspack v1.0.0-alpha.0, but faced a blocker: One of my custom webpack plugins uses the assets info metadata in the processAssets compilation hook, but it's undefined in Rspack.

My use case: Besides regular webpack entries, my setup also processes pre-webpack legacy JS, CSS, and image files added via CopyWebpackPlugin: their content hash gets added into the file name, and on processAssets I generate a map of original sourceFilenames to the new paths, which is then used by a non-Node.js server to replace original file references with the ones that have content hash in them.

Is there another way to get the assets info metadata in Rspack? If not, is it something you can add?

Thank you.

What does the proposed API of configuration look like?

Example:

compilation.hooks.processAssets.tap(
  {
    /** … */
  },
  (assets) => {
    Object.entries(assets).forEach(([pathname, source]) => {
      const assetInfo = compilation.assetsInfo.get(pathname);
      // @todo: do something with "pathname", "source" and "assetInfo"
    });
  }
);

See https://webpack.js.org/api/compilation-hooks/#assets-info

wxiaoyun commented 2 months ago

If you are trying to append and access additional information to AssetInfo object across compilation stages, please use compilation.updateAsset to update assetInfo, compilation.getAsset or compilation.getAssets to retrieve assetInfo.

These getters and setters are necessary for bidirectional conversion between Js Object and their corresponding Rust structs under the hood. Otherwise, only knownAssetInfo will be persisted across compilation stages:

image

Let me know if this helped

iusesoftware commented 2 months ago

This helped, thank you. I was able to move forward a little bit.

1) Do you know if there is a way to replace compilation.assets[ filePath ].source()? I searched for updateAsset in the repo and found that there is also getAssetSource, but seems like it's internal only.

2) Also, I wasn't able to confirm this fully, because I'm getting Process finished with exit code -1073741571 (0xC00000FD) (stack overflow?). I understand this is probably not related, but may be you know what may be causing this?

wxiaoyun commented 2 months ago

1) Do you know if there is a way to replace compilation.assets[ filePath ].source()? I searched for updateAsset in the repo and found that there is also getAssetSource, but seems like it's internal only.

Calling compilation.getAsset(<name>) or compilation.getAssets() gives you Asset object with source

    getAssets(): ReadonlyArray<Asset> {
        const assets = this.#inner.getAssets();

        return assets.map(asset => {
            return Object.defineProperties(asset, {
                info: {
                    value: JsAssetInfo.__from_binding(asset.info)
                },
                source: {
                    get: () => this.__internal__getAssetSource(asset.name)
                }
            }) as unknown as Asset;
        });
    }
wxiaoyun commented 2 months ago

2) Also, I wasn't able to confirm this fully, because I'm getting Process finished with exit code -1073741571 (0xC00000FD) (stack overflow?). I understand this is probably not related, but may be you know what may be causing this?

I am not sure what it means. Can you create a minimal reproduction that triggers this error?

iusesoftware commented 2 months ago
  1. Do you know if there is a way to replace compilation.assets[ filePath ].source()? I searched for updateAsset in the repo and found that there is also getAssetSource, but seems like it's internal only.

Calling compilation.getAsset(<name>) or compilation.getAssets() gives you Asset object with source

  getAssets(): ReadonlyArray<Asset> {
      const assets = this.#inner.getAssets();

      return assets.map(asset => {
          return Object.defineProperties(asset, {
              info: {
                  value: JsAssetInfo.__from_binding(asset.info)
              },
              source: {
                  get: () => this.__internal__getAssetSource(asset.name)
              }
          }) as unknown as Asset;
      });
  }

One custom plugin emits a new asset with:

const fileContents = JSON.stringify( fileListJson );

compilation.emitAsset( fullPath, new compiler.webpack.sources.RawSource( fileContents ) );

And in another custom plugin I tried:

compilation.getAsset( filePath ).source(); // didn't work
compilation.getAsset( filePath ).source.get(); // didn't work
compilation.getAsset( filePath ).source.toString(); // didn't work

Only this worked, but seems like it's a band-aid:

const fileMapSource = compilation.getAsset( filePath ).source._valueAsString; // did work

Its usage is:

const fileMap = JSON.parse( fileMapSource );

I'm on v1.0.0-alpha.2 now.

iusesoftware commented 2 months ago

Is there access to compilation.valueCacheVersions?

I only found this:

My usage in the same custom plugin:

const foundAsset = findAsset( pathInMap, compilation.assetsInfo );

if ( foundAsset ) {
    let assetPathPrefix = '';

    for ( const [ key, value ] of compilation.valueCacheVersions ) {
        // @todo Find less error-prone way.
        if ( key.includes( 'webpack/DefinePlugin process.env.ASSET_PATH' ) ) {
            assetPathPrefix = value
                // “/” gets (re)added later
                // @todo This is all dirty. Find a better way.
                .replace( '/', '' )
                .replace( /"/gu, '' );
        }
    }

    filePath = `${ assetPathPrefix }${ foundAsset }`;
}
h-a-n-a commented 2 months ago

And in another custom plugin I tried:

compilation.getAsset( filePath ).source(); // didn't work
compilation.getAsset( filePath ).source.get(); // didn't work
compilation.getAsset( filePath ).source.toString(); // didn't work

Only this worked, but seems like it's a band-aid:

const fileMapSource = compilation.getAsset( filePath ).source._valueAsString; // did work

You were using the wrong API. Check out webpack-sources.