esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

Add ability to support import map WICG proposal

Open lgollut opened this issue 2 years ago • 3 comments

Currently the hash functionality used to create the module graph implies that when the content of a single chunk changes, all importers of that chunk see their hash updated, invalidating browsers caching although not really necessary.

Consider a simple module graph with two entry points a.js and b.js both depending on chunk.js. If the content of chunk.js is updated, its hash will change, requiring changes in both a.js and b.js import statement and thus updating their hashes although this is not necessary (nor desired) for the caching mechanism.

The import map proposal described here mitigate this issue by providing a mechanism to decouple the module specifiers from the actual URL of the resource. As stated in Mapping away hashes in script filenames section, import statements keep referencing the non-hashed module specifiers (and therefore don't interfere with their own module hash), while the import map is responsible to translate it to its corresponding hashed resource.

As far as I know, it's currently not feasible in esbuild since the hash is enabled or disabled by providing the [hash] in the template string of entryNames, chunkNames and assetNames options. And the hash is then applied both to the file names and import statements of the module graph without any touch point to modify that behavior.

In order to support import map, we should be able to have hashed output filenames along with non-hashed import statements and deterministic chunk names (currently only hashes differentiate chunk files). Maybe by always providing hashes in the metafile we could achieve the desired effect with the plugin api (apart for chunk names).

lgollut avatar May 06 '22 08:05 lgollut

FWIW this would be extremely valuable for us @evanw. Currently each deployment we make creates hundreds of new files, even if nothing actually changed in the code (our issue is that we need to ship a deployment version number in our bundle, which changes every time we deploy)

ephemer avatar Jun 24 '22 15:06 ephemer

Same issue here. Quick fix.

function importMapPlugin(
  importMap: ImportMap,
  userAgent: string,
): esbuild.Plugin {
  return {
    name: importMapPlugin.name,
    setup(build) {
      build.onResolve({ filter: /^.*$/ }, (args) => {
        if (!(args.path in importMap.imports)) {
          return null;
        }
        const el = importMap.imports[args.path];
        if (!el) return;

        if (el.startsWith("https://")) {
          return {
            path: el,
            namespace: "http-url",
            external: true,
          };
        }
        return {
          path: importMap.imports[args.path]?.replace(
            /^file:\/\//,
            "",
          ) as string,
        };
      });

      build.onResolve({ filter: /.*/, namespace: "http-url" }, (args) => ({
        path: new URL(args.path, args.importer).toString(),
        namespace: "http-url",
      }));

      build.onLoad({ filter: /.*/, namespace: "http-url" }, async (args) => {
        const contents = new Uint8Array(
          await (await fetch(args.path, {
            headers: { "user-agent": userAgent },
          }))
            .arrayBuffer(),
        );
        return {
          contents,
        };
      });
    },
  };
}
    importMapPlugin(JSON.parse(await Deno.readTextFile("./import_map.json")), 'deno'),
  ],
}).catch(() => Deno.exit(1));

esbuild.stop();

loynoir avatar Oct 04 '22 13:10 loynoir