React 17 + Craco环境下图片与字体预加载与代码分割冲突问题求助
我之前也踩过这个坑!用React 17搭配Craco的时候,手动在index.html里写preload标签确实会因为打包后资源被加上哈希后缀而失效。给你几个亲测有效的解决办法:
办法一:自定义HTML模板注入正确资源路径
这个思路是利用Craco修改webpack的html-webpack-plugin配置,让模板能拿到webpack处理后的带哈希的资源文件名:
- 把你原来的
public/index.html改名为public/index.ejs(变成模板文件) - 在
craco.config.js里添加如下配置:
module.exports = { plugins: [ { plugin: { overrideWebpackConfig: ({ webpackConfig }) => { // 找到html-webpack-plugin的实例 const htmlPlugin = webpackConfig.plugins.find( plugin => plugin.constructor.name === 'HtmlWebpackPlugin' ); if (htmlPlugin) { // 指定自定义模板文件 htmlPlugin.options.template = './public/index.ejs'; // 传入你需要预加载的资源列表 htmlPlugin.options.preloadAssets = [ { url: require.resolve('./src/assets/images/fooImage.png'), as: 'image', crossorigin: true }, // 可以添加更多资源,比如字体 // { url: require.resolve('./src/assets/fonts/your-font.woff2'), as: 'font', crossorigin: true } ]; } return webpackConfig; }, }, }, ], };
- 在
index.ejs里替换原来手动写的preload标签,改成循环渲染:
<% preloadAssets.forEach(asset => { %> <link rel='preload' as='<%= asset.as %>' href='<%= htmlWebpackPlugin.files.publicPath %><%= asset.url.split('/').pop() %>' <%= asset.crossorigin ? 'crossorigin' : '' %> /> <% }) %>
这样打包后,模板会自动输出带正确哈希文件名的preload标签。
办法二:用PreloadWebpackPlugin自动生成预加载标签
这个更省心,不用手动维护资源列表,插件会自动识别webpack打包后的资源并生成preload标签:
- 先安装插件:
npm install preload-webpack-plugin --save-dev
- 修改
craco.config.js:
const PreloadWebpackPlugin = require('preload-webpack-plugin'); module.exports = { plugins: [ { plugin: { overrideWebpackConfig: ({ webpackConfig }) => { webpackConfig.plugins.push( new PreloadWebpackPlugin({ rel: 'preload', include: 'initial', // 预加载初始加载的资源 fileBlacklist: [/\.map$/, /hot-update\.js$/], // 排除不需要的文件 as(entry) { // 根据文件类型自动设置as属性 if (/\.png$|\.jpg$/.test(entry)) return 'image'; if (/\.woff2$|\.woff$/.test(entry)) return 'font'; return 'script'; // 默认类型 }, }) ); return webpackConfig; }, }, }, ], };
打包后,插件会自动在生成的index.html里插入正确的preload标签,完全不用手动写。
办法三:在React组件中动态创建预加载标签
如果你的资源是和某个组件绑定的,或者不想改webpack配置,可以在组件里动态生成preload标签:
import { useEffect } from 'react'; import fooImage from './assets/images/fooImage.png'; // 如果是字体,也要import进来 // import yourFont from './assets/fonts/your-font.woff2'; function App() { useEffect(() => { // 创建图片预加载标签 const imagePreload = document.createElement('link'); imagePreload.rel = 'preload'; imagePreload.as = 'image'; imagePreload.href = fooImage; imagePreload.crossOrigin = ''; document.head.appendChild(imagePreload); // // 创建字体预加载标签 // const fontPreload = document.createElement('link'); // fontPreload.rel = 'preload'; // fontPreload.as = 'font'; // fontPreload.href = yourFont; // fontPreload.crossOrigin = 'anonymous'; // document.head.appendChild(fontPreload); // 组件卸载时清理标签 return () => { document.head.removeChild(imagePreload); // document.head.removeChild(fontPreload); }; }, []); return <div>你的组件内容</div>; } export default App;
这种方式的好处是直接利用React的import机制拿到处理后的资源路径,不用关心哈希后缀。
内容的提问来源于stack exchange,提问作者nima
相关产品推荐
相关产品推荐

