【问题标题】:React Native Expo : Is there a way to cache the content of a whole directory instead of having to individually write the path of each fileReact Native Expo : Is there a way to cache the content of a whole directory instead of having to individually write the path of each file
【发布时间】:2022-12-27 00:31:21
【问题描述】:

Usually, you would need to do something similar to this to cache videos and pictures:

SplashScreen.preventAutoHideAsync();

function cacheAssets(images) {
  return images.map((image) => {
    if (typeof image === "string") {
      return Image.prefetch(image);
    } else {
      return Asset.fromModule(image).downloadAsync();
    }
  });
}

export default function App() {
  const [appIsReady, setAppIsReady] = useState(false);

  // Load any resources or data that you need prior to rendering the app
  useEffect(() => {
    async function loadResourcesAndDataAsync() {
      try {
        const Assets = cacheAssets([
          require("./app/assets/salutt_splash_video_no_tagline.mp4"),
          require("./app/assets/AppCustomBackgroundsFrench/LoginOptionsFR1.png"),
          require("./app/assets/AppCustomBackgroundsFrench/SignInFR1.png"),
          etc.,

        ]);

        await Promise.all([...Assets]);
      } catch (e) {
        // You might want to provide this error information to an error reporting service
        console.warn(e);
      } finally {
        setAppIsReady(true);
        setTimeout(SplashScreen.hideAsync, 400);
      }
    }

    loadResourcesAndDataAsync();
  }, []);

  if (!appIsReady) {
    return null;
  }

Is there a way to just put the name of the directory or use recursive wildcards like **/*.png to chose a bunch of files at the same time?

【问题讨论】:

  • I don't think it's possible, but what I did in a similar situation: I put a script in the project which traverses through folders and subfolders in assets, takes note of all existing files in their und generates a JS/TS file with a require statement for each single asset file. It's even possible that there're libraries for that.
  • If found some time to provide a longer example. I hope it helps!

标签: javascript reactjs react-native caching expo


【解决方案1】:

I think that's not possible. Here's what I came up with instead. In tools/assetBundler.ts:

import { resolve as resolvePath, relative as relativePath } from 'path';
import * as fs from 'fs';

import * as path from 'path';

const generatedFolderName = generated

/**
 * Generates `require`ments for all assets, so they will get bundled.
 */
const run = async () => {
  try {
    const inputFolder: string = resolvePath('.', 'app', 'assets');
    const targetFolder: string = resolvePath('.', 'app', 'assets', 'assetManager', generatedFolderName);
    const fileNames = await getAllFileNames(inputFolder);

    await ensureEmptyFolder(targetFolder);

    const mapEntries: string[] = [];
    for (const fileName of fileNames)
      mapEntries.push(`['${fileName}', {asset: require('${fileName.replace('app/assets', '../..')}')}],`);
    const bundlerContent = `/**
 * This file was automatically generated by the asset bundler script.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source asset and the data files,
 * and run this script to regenerate this file.
 */
 
 export const assetsByName = new Map<string, {asset: any}>([
${mapEntries.join('
')}
]);
`;
    return fs.promises.writeFile(resolvePath(targetFolder, 'assetMap.ts'), bundlerContent);
  } catch (e: unknown) {
    console.log("Couldn't generate asset bundler:", e);
  }
};

const ignorePattern = /^.|w*.(ts|js)$/m;

/**
 * Based on https://stackoverflow.com/a/45130990/5767484, this method returns a list of all files names in the given folder and subfolders.
 * @param dir the folder where to find the files in
 * @returns the list of all files found
 */
const getAllFileNames: (dir: string) => Promise<string[]> = async (dir) => {
  const dirents = await fs.promises.readdir(dir, { withFileTypes: true });
  const files = await Promise.all(
    dirents.map((dirent) => {
      let fileName: string | null = null;
      if (!ignorePattern.test(dirent.name))
        fileName = relativePath(process.cwd(), resolvePath(dir, dirent.name));
      return dirent.isDirectory() ? getAllFileNames(resolvePath(dir, dirent.name)) : [fileName];
    })
  );
  return files.flat().filter(notEmpty);
};

/**
 * Makes sure the given folder exists and is empty. Note that existing files in the folder we be deleted in case `enforceEmpty` isn't set to true.
 * @param folder to be created and emptied
 * @param enforceEmpty if `true`, existing files in the folder will be deleted
 */
const ensureEmptyFolder = async (folder: string, enforceEmpty: boolean = true) =>
  fs.promises
    .mkdir(folder, { recursive: true })
    .then((_) => (enforceEmpty ? fs.promises.rm(folder, { recursive: true, force: true }) : undefined))
    .then((_) => fs.promises.mkdir(folder))
    .catch((e) => {
      if (enforceEmpty || e.code !== 'EEXIST') throw e;
    });

/** Filter for `null` and `undefined` providing type-safety.*/
function notEmpty<TValue>(value: TValue | null | undefined): value is TValue => value != null

(async () => run())();

To run this tool, you can create npm scripts in your package.json like:

"scripts": {
    "asset-bundler": "ts-node tools/generateAssetBundler.ts",
    "bundleStart": "npm run asset-bundler && expo start "
}

This way you'll get a file like

export const assetsByName = new Map<string, { asset: any }>([
  ['app/assets/salutt_splash_video_no_tagline.mp4', { asset: require('../../app/assets/salutt_splash_video_no_tagline.mp4') }],
  [ ... ],
...

in app/assets/assetManager/assetMap.ts, and you can import your assets by their qualified name, e.g.

const splashVid = assetsByName.get('app/assets/salutt_splash_video_no_tagline.mp4')`

You can create an assetManager who hold the assetsByName map internally if you need convenience functions etc. Note that my code is in TypeScript - I'ld heavily recommend you to use TypeScript, but it can by converted to JavaScript rather easily. Also be careful about the pathes. They might differ a bit for you, so you might have to adjust them accordingly.

【讨论】:

    猜你喜欢
    • 2022-07-14
    • 2022-12-27
    • 2022-12-02
    • 2022-12-26
    • 2022-12-02
    • 2022-12-02
    • 2022-12-26
    • 2023-02-01
    • 2022-12-27
    相关资源
    最近更新 更多