Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow file loader to resolve paths for files that do not exist at run time #8934

Open
vveisard opened this issue Feb 15, 2024 · 3 comments
Open
Labels
bundler Something to do with the bundler enhancement New feature or request

Comments

@vveisard
Copy link
Contributor

vveisard commented Feb 15, 2024

What is the problem this feature would solve?

My goal is to use an import to resolve a file path for an asset that does not exist, generate an asset file at that file path, and include that generated asset file in the build. I cannot achieve this, because the file loader throws an error when the file is missing, and there is no mechanism to configure the file loader to simply resolve the path.

import backgroundImageUrl from "./assets/background.png";
import renderImageUrl from "./assets/render.webp";

const thisContentModuleBaseContentId: ContentId = "mario";

const thisCharacterContentData: CharacterContentData = {
  backgroundImageUrl: backgroundImageUrl,
  renderImageUrl: renderImageUrl,
};

const thisContentModuleData: ContentModuleData = {
  foo: {
    ids: [thisContentModuleBaseContentId],
    datas: {
      [thisContentModuleBaseContentId]: thisCharacterContentData,
    },
  },
};

export default thisContentModuleData;
import { Resvg } from "@resvg/resvg-js";
import { createSvgDocumentText } from "tool-create-svg";
//
import type { CharacterContentData } from "lib-core";
//
import officialContentModule from "../src/index.ts";

for (const iCharacterContentId of officialContentModule.characters.ids) {
  const iCharacterContentData: CharacterContentData =
    officialContentModule.characters.datas[iCharacterContentId];

  const iCharacterContentDataSVGDocument = createSvgDocumentText(
    iCharacterContentData
  );

  const resvg = new Resvg(iCharacterContentDataSVGDocument);
  const pngData = resvg.render();
  const pngBuffer = pngData.asPng();

  await Bun.write(iCharacterContentData.renderImageUrl, pngBuffer);
}

What is the feature you are proposing to solve the problem?

I am proposing the file loader should not validate that the file exists at run time (but should still validate at build time).

Alternatively, a richer feature would be a follow-up to #5519 to allow configuration of loaders using import attributes. Something like

import imageUrl from "./assets/background.png" with { loader: "file", validateExists: 'build' }.

This would tell Bun to use the "file" loader, and to only validate if the file exists at build time. By default, validateExists would be "always", which would throw an error during run time (current behavior).

What alternatives have you considered?

There is no alternative for this. I am considering implementing my own file loader plugin.

@vveisard vveisard added the enhancement New feature or request label Feb 15, 2024
@vveisard
Copy link
Contributor Author

So I was right, a custom loader did the job:

import {
  type ContentId,
  type ContentModuleData,
  type CharacterContentData,
} from "lib-core";
import backgroundImagePath from "./assets/background.png";
import renderImagePath from "./assets/render.webp";

const backgroundImageUrl = new URL(backgroundImagePath, import.meta.url);
const renderImageUrl = new URL(renderImagePath, import.meta.url);
const thisContentModuleBaseContentId: ContentId = "mario";

const thisCharacterContentData: CharacterContentData = {
  backgroundImageUrl: backgroundImageUrl,
  renderImageUrl: renderImageUrl,
};

const thisContentModuleData: ContentModuleData = {
  characters: {
    ids: [thisContentModuleBaseContentId],
    datas: {
      [thisContentModuleBaseContentId]: thisCharacterContentData,
    },
  },
};

export default thisContentModuleData;
import { plugin, type BunPlugin } from "bun";

const filePathResolver: BunPlugin = {
  name: "File path resolver",
  setup(build) {
    build.onResolve({ filter: /\.(webp)$/ }, async (args) => ({
      path: args.path,
    }));
  },
};

plugin(filePathResolver);
import { plugin } from "bun";
import { Resvg } from "@resvg/resvg-js";
import { createSvgDocumentText } from "tool-create-svg";
//
import { filePathResolverPlugin } from "../plugin.ts";
import type { CharacterContentData } from "lib-core";

plugin(filePathResolverPlugin); // file resolver

// async import, to import *after* the plugin is registered
const officialContentModule = (await import("../src/index.ts")).default;

for (const iCharacterContentId of officialContentModule.characters.ids) {
  const iCharacterContentData: CharacterContentData =
    officialContentModule.characters.datas[iCharacterContentId];

  const iCharacterContentDataSVGDocument = createSvgDocumentText(
    iCharacterContentData
  );

  const resvg = new Resvg(iCharacterContentDataSVGDocument);
  const pngData = resvg.render();
  const pngBuffer = pngData.asPng();

  await Bun.write(iCharacterContentData.renderImageUrl, pngBuffer);
}

However, this is not configurable using import attributes like I want (would need #7239). I'll have to settle for adding "query parameters" to the end of the import specifier.

@paperclover paperclover added the bundler Something to do with the bundler label Feb 16, 2024
@vveisard
Copy link
Contributor Author

After exploring this a bit, I think this use case would be addressed by #2472 and #2906.

@fatlard1993
Copy link

I wanted something similar; I wanted to be able to import any file (like a javascript file) but as plain text. I was able to support this via a custom non-existent file extension. To bypass the requirement that buns resolver finds a real file I have to create an empty file in the onResolve function and then immediately spawn an async unlink to clean the file up after the synchronous onResolve function returns:

import { writeFileSync, unlink } from 'node:fs';
import { dirname, resolve } from 'node:path';

const onResolve = ({ path, importer }) => {
	path = resolve(dirname(importer), path);

	writeFileSync(path, '');

	unlink(path, error => error && console.error(error));

	return { path };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bundler Something to do with the bundler enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants