我一直在研究向我的Razzle服务器添加中间件的最佳方法,以检测它何时服务于Razzle构建的文件,该文件在文件名中包含Webpack [hash:8]
或[contenthash:8]
。 我首先讨论了我在这里遇到的一些问题https://github.com/jaredpalmer/razzle/pull/1368#issuecomment -664015050
我希望Razzle以易于使用的方式生成和公开被认为是“不可变的”(出于在响应中设置Cache-Control
标头的目的)安全的文件/资产列表,而无需进行额外的转换chunks.json和/或assets.json文件
注意:设置长效且不可变的缓存控制响应时,我想避免对文件是否可以视为不可变进行任何“近似”处理(AKA正则表达式以检测文件名中的哈希值),因为假阳性可能导致一个文件被长时间不变地缓存,并且不能被服务器端缓存失效修复,这可能是一个非常痛苦的问题。
TL; DR为什么尝试使用当前公开的json文件很困难:
chunks.json
和assets.json
。 chunks.json包含源地图文件,assets.json具有png / fonts等文件,chunks.json没有。(assets.json).client
任何块(例如: "client": { "js": "/static/js/bundle.6fc534aa.js" }
),assets.json将所有其他资产分组在一个空字符串下(例如: "": { "js": "/static/js/0.cb47cee9.chunk.js" }
)。"client": { "css": ["filename.css"] }
),如果assets.json中仅存在一个文件文件,它将改为单个字符串(例如: "client": { "css": "filename.css" }
)。"json": "/../chunks.json"
,这不是我认为应该包含的内容(我不确定这是否是错误),但是在列出以下内容时,我必须手动将其删除可以提供长期有效的cache-Control响应标头的文件。chunks: ["1", "2", "3"]
数组添加到chunks.json的计划有些烦人,因为这意味着我必须做一些额外的工作来过滤掉(chunks.json).client.chunks
因为它不包含像这样的文件数组(chunks.json).client.css
和(chunks.json).client.js
等。client
块中的文件甚至都没有出现在chunks.json
文件中。 我进行/建议更改以将其更改为使用块编号作为键,因为至少它们随后出现在文件中。 不利的一面是,现在chunks.json
和assets.json
在处理不是主要命名块( "client": {/* blah */ }
)的块时,其模式进一步不同。目前,我使用的是assets.json和chunks.json。
我还没有:
"chunks": ["1", "2", "3"]
和"json": "/../chunks.json"
function razzleCacheableFiles() {
// TODO: Add loading the assets.json file to support (png/txt files etc)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const chunks = require(process.env.RAZZLE_CHUNKS_MANIFEST!);
const filesByType = Object.entries(chunks).reduce(
(chunkAcc: any, [, chunk]) => {
const types = Object.entries(chunk as any).reduce(
(typeAcc, [fileType, files]) => {
return {
[fileType]: chunkAcc[fileType]
? [...chunkAcc[fileType], ...(files as string[])]
: files,
};
},
{},
);
return types;
},
{},
);
const files = Object.entries(filesByType).reduce(
(acc: any[], [, files]) => [...acc, ...(files as string[])],
[],
);
return files;
}
const cacheableFiles = razzleCacheableFiles();
// Serve static files located under `process.env.RAZZLE_PUBLIC_DIR`
const assetCaching = {
immutable: {
maxAge: CacheFor.OneMonth,
sMaxAge: CacheFor.OneYear,
},
default: {
maxAge: CacheFor.OneDay,
sMaxAge: CacheFor.OneWeek,
}
};
app.use(
serve(process.env.RAZZLE_PUBLIC_DIR!, {
setHeaders(res, path) {
const filename = path.replace(process.env.RAZZLE_PUBLIC_DIR!, "");
const hasHashInFilename = cacheableFiles.includes(filename);
if (hasHashInFilename) {
const { immutable } = assetCaching;
res.setHeader(
"Cache-Control",
`max-age=${immutable.maxAge},s-maxage=${immutable.sMaxAge},immutable`,
);
return;
}
res.setHeader(
"Cache-Control",
`max-age=${assetCaching.default.maxAge},s-maxage=${asetCaching.default.sMaxAge}`,
);
},
}),
);
简洁明了的描述当前行为/用途。
可能有很多方法可以做到这一点,但是我想要的主要事情只是一种加载由Razzle build生成的所有可缓存/不可变资产的数组的方法。 结果可能如下所示:
// File: caching.json
// contains all files/assets with a hash in them regardless of what type of file they are.
{
"immutable": [
"/static/js/0.cb47cee9.chunk.js",
"/static/js/0.cb47cee9.chunk.js.map",
"/static/js/0.cb47cee9.chunk.js.LICENSE.txt",
"/static/media/ferris-error.407b714e.png"
],
// I'm not even sure if this is required because I don't think razzle generates any files that don't have hashes in them?
// possibly files copied in from the `public/` directory during build. but I'm not even sure if it'd that'd useful
"standard": []
}
// RAZZLE_CACHING_MANIFEST is probably a silly name but
const cacheableFiles = require(process.env.RAZZLE_CACHING_MANIFEST!);
// Serve static files located under `process.env.RAZZLE_PUBLIC_DIR`
const assetCaching = {
immutable: {
maxAge: CacheFor.OneMonth,
sMaxAge: CacheFor.OneYear,
},
default: {
maxAge: CacheFor.OneDay,
sMaxAge: CacheFor.OneWeek,
}
};
app.use(
serve(process.env.RAZZLE_PUBLIC_DIR!, {
setHeaders(res, path) {
const filename = path.replace(process.env.RAZZLE_PUBLIC_DIR!, "");
const hasHashInFilename = cacheableFiles.immutable.includes(filename);
if (hasHashInFilename) {
const { immutable } = assetCaching;
res.setHeader(
"Cache-Control",
`max-age=${immutable.maxAge},s-maxage=${immutable.sMaxAge},immutable`,
);
return;
}
res.setHeader(
"Cache-Control",
`max-age=${assetCaching.default.maxAge},s-maxage=${asetCaching.default.sMaxAge}`,
);
},
}),
);
我尚未完全研究好的解决方案,但是在尝试使用assets.json
和chunks.json
在运行时将“可缓存资产”列表汇总在一起后,我非常相信最小的最佳方法是在构建时使用某种Webpack插件,并绕过这两个文件的不一致之处。
出于我的目的,我可能最初会开始研究如何使用插件而不是像我一直在运行时那样来完成此任务,但是我认为默认情况下使它醒目具有很大的价值。 能够对散列文件设置长寿命的缓存控制是很大程度上是为什么它们开始被散列的原因,因此公开所有这些文件的列表似乎是适当的。
任何想要为razzle生成和散列的文件设置适当的长期且不变的缓存控制响应标头的用户。
chunks.json
和assets.json
(似乎容易出错和易碎),在运行时生成所有不可变/可缓存文件的列表。require(process.env.RAZZLE_CACHING_MANIFEST!)
。 ()我愿意为做出这一更改提供帮助/帮助,但我可能需要在正确的方向上有所作为(当然,无论是否接受或欢迎这一更改)。
还有一个想法,有这样的事情可能会使在确保事物使用[contenthash:8]
而不是[hash:8]
(构建哈希)时(如果可以)时更容易进行一些测试/稳定性( https:/// /github.com/jaredpalmer/razzle/issues/1331
这似乎是一个值得的主意。
它还与另一个问题有关,即优化中的chunkGroup配置。 因为如果设置了该字符串,则该空字符串将为“共享”,“框架”等。
如果您查看next.js,它们将使用类似chunkGroups的配置。
当我们更改此设置时,它也将向后不兼容,但必须这样做。 我正在进行一些较大的更改,这些更改也需要进行重大发布。
但是请随时提出一些解决此问题的代码
如果您查看next.js,它们将使用类似chunkGroups的配置。
哦,很酷,我不知道是否/是否有其他工具/框架可以实现这一目标,您是否有链接/示例?
它还与另一个问题有关,即优化中的chunkGroup配置
一个公开的问题? 你能指出我哪一个,以便我能获得更多的背景吗?
我确实认为解决此问题的潜在方法之一是更强地定义现有chunks.json
和assets.json
的形状/架构。 可能需要仔细考虑(并且有一个主要的版本改进),但是如果存在其他框架等如何解决了问题的示例,则遵循类似的方向可能是有意义的
和
https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack-config.ts#L378
不确定它们如何执行清单。
现在看看https://github.com/jaredpalmer/razzle/issues/1377,添加了一个新示例:)
所有文件现在都使用contenthash。 当我们有捆绑器时,我要说的复制文件是一种不好的做法。
我不确定我的意思是“当我们有捆绑器时,我要说的复制文件是不好的做法”。
当前的行为是,如果将任何文件放在Razzle项目的顶级public/
文件夹中。
build/
public/
robots.txt
manifest.json
package.json
当您razzle build
时,它们会陷入静态资产
build/
public/
robots.txt
manifest.json
static/
...
public/
robots.txt
manifest.json
package.json
我当时认为可能需要维护在构建期间复制的所有资产的列表,以便可以将它们分别单独地定位到目标以应用缓存控制。
我认为反对的观点(我能想到的)是,用户可能不需要区分在构建过程中Razzle复制的文件与可能在razzle build
之外手动放置的文件之间的区别。
我认为public只应包含robots.txt和favicon.ico,并且不会使用哈希对其进行版本控制。
其他任何东西都应该由webpack捆绑在一起。 任何较大的网站图标都应捆绑在一起。
也许可以,但是即使您想使用默认的create-react-app
保持“即插即用”兼容性,也可能值得考虑的是,该应用程序清单和一些图标也将出现在其中。
我非常清楚地记得manifest.json
/ manifest.webmanifest
不应包含构建哈希的原因,这是打包程序经常将其排除在外的原因之一。 我可能记错了/记错了,但可能与PWA和脱机模式有关
是否有任何出色的示例项目实现了PWA(和/或服务工作者)支持?
也许不太重要,但过去使用create-react-app时放在public/
文件夹中的其他一些东西是与网站相关的可下载文件,但需要永久URL。 就像拥有可以在发送电子邮件等时链接到的pdf文档一样🤷
尝试查找if / why /何时将webmanifests与捆绑程序分开的示例:
该帖子中有一条评论链接到https://github.com/w3c/manifest/issues/446#issuecomment -351368893
是的,可下载文件应该放在那儿。 嗯,但是如何将这些文件添加到assets.json? 有任何想法吗? pack我们应该让webpack找到它们并按原样捆绑吗? 修改assets.json似乎有点黑。
我不认为有PWA示例。 但是如果他们需要一个一致的名字。 这需要由webpack处理。
我将用清单插件替换资产插件,以便我们调整输出。
添加了一个包含所有文件的新资产清单https://github.com/jaredpalmer/razzle/commit/1c6e9169e9d8eee256d0f118f8a88da8de85989f有任何改进建议吗?
现在发布了金丝雀:)
我看到清单插件并未真正维护。 最好的办法就是自己做。 但是我目前不认识任何人,但是(也许)我或可以做到这一点的webpack人士。
现在添加到金丝雀分支。 现在有点骇人听闻。 但这是可行的,并且是可以改进的起点。
经过一番考虑,我不会将其添加到核心中。
但是这是我想出的代码:
new ManifestPlugin({
fileName: path.join(paths.appBuild, 'assets.json'),
writeToFileEmit: true,
generate: (seed, files) => {
const entrypoints = new Set();
const noChunkFiles = new Set();
const longTermCacheFiles = new Set();
files.forEach(file => {
if (file.isChunk) {
const groups = (
(file.chunk || {})._groups || []
).forEach(group => entrypoints.add(group));
} else {
noChunkFiles.add(file);
}
if (!webpackOptions.fileLoaderExclude.some(re=>re.test(file.path))) {
let fileHasHash = /\[(build|content)?hash/.test(
typeof webpackOptions.fileLoaderOutputName == 'function' ?
webpackOptions.fileLoaderOutputName(file) : webpackOptions.fileLoaderOutputName);
if (fileHasHash) longTermCacheFiles.add(file);
} else if (webpackOptions.urlLoaderTest.some(re=>re.test(file.path))) {
let urlHasHash = /\[(build|content)?hash/.test(
typeof webpackOptions.urlLoaderOutputName == 'function' ?
webpackOptions.urlLoaderOutputName(file) : webpackOptions.urlLoaderOutputName);
if (urlHasHash) longTermCacheFiles.add(file);
} else if (webpackOptions.cssTest.some(re=>re.test(file.path))) {
let cssHasHash = /\[(build|content)?hash/.test(
typeof webpackOptions.cssOutputFilename == 'function' ?
webpackOptions.cssOutputFilename(file) : webpackOptions.cssOutputFilename);
if (cssHasHash) longTermCacheFiles.add(file);
} else if (webpackOptions.jsTest.some(re=>re.test(file.path))) {
let jsHasHash = /\[(build|content)?hash/.test(
typeof webpackOptions.jsOutputFilename == 'function' ?
webpackOptions.jsOutputFilename(file) : webpackOptions.jsOutputFilename);
if (jsHasHash) longTermCacheFiles.add(file);
}
});
const entries = [...entrypoints];
const entryArrayManifest = entries.reduce((acc, entry) => {
const name =
(entry.options || {}).name ||
(entry.runtimeChunk || {}).name ||
entry.id;
const allFiles = []
.concat(
...(entry.chunks || []).map(chunk =>
chunk.files.map(path => config.output.publicPath + path)
)
)
.filter(Boolean);
const filesByType = allFiles.reduce((types, file) => {
const fileType = file.slice(file.lastIndexOf('.') + 1);
types[fileType] = types[fileType] || [];
types[fileType].push(file);
return types;
}, {});
const chunkIds = [].concat(
...(entry.chunks || []).map(chunk => chunk.ids)
);
return name
? {
...acc,
[name]: { ...filesByType, chunks: chunkIds },
}
: acc;
}, seed);
entryArrayManifest['noentry'] = [...noChunkFiles]
.map(file => file.path)
.reduce((types, file) => {
const fileType = file.slice(file.lastIndexOf('.') + 1);
types[fileType] = types[fileType] || [];
types[fileType].push(file);
return types;
}, {});
entryArrayManifest['cacheable'] = [...longTermCacheFiles]
.map(file => file.path);
return entryArrayManifest;
},
})
但是我学到了很多关于资产的知识;)
抱歉,我有一段时间没有花很多时间,但这看起来很整洁。 现在来看一下将我的东西升级到最新的稳定Razzle版本,并尝试将您的建议作为自定义插件。
看起来不错,但是我对此有些困惑:
let fileHasHash = /\[(build|content)?hash/.test(
typeof webpackOptions.fileLoaderOutputName == 'function'
? webpackOptions.fileLoaderOutputName(file)
: webpackOptions.fileLoaderOutputName);
if (fileHasHash) longTermCacheFiles.add(file);
webpackOptions.fileLoaderOutputName
是什么意思? 对我来说,它总是似乎是不确定的。
只有在喧闹的金丝雀中
整洁,我现在在我的项目中建立一个分支,在canary分支上工作取得了一些进展。 这还不太正常,目前我的问题似乎主要与配置babel加载程序以识别同级程序包有关。 我可以构建,但是在尝试运行它时遇到“找不到模块”的问题。
这可能不太有趣/有用,但是:
https://github.com/bootleg-rust/sites/pull/2/files
从内存中我最初是从https://github.com/jaredpalmer/razzle/issues/664借来的配置
/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/lib-ssr-runtime sync:2
var e = new Error("Cannot find module '" + req + "'");
^
Error: Cannot find module 'undefined'
at require (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/lib-ssr-runtime sync:2:10)
at razzleCacheableFiles (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/lib-ssr-runtime/server.tsx:106:18)
at createKoaApp (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/lib-ssr-runtime/server.tsx:61:26)
at Module.call (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/src/server.tsx:42:13)
at a (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/webpack/bootstrap:19:22)
at Object.call (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/server.js:1:31123)
at __webpack_require__ (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/webpack/bootstrap:19:22)
at /Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/webpack:/webpack/bootstrap:83:10
at Object.<anonymous> (/Users/jstableford/Desktop/@bootleg-rust/sites/packages/web-rust-lang/build/server.js:1:935)
https://github.com/jaredpalmer/razzle/issues/1459
并设置NODE_PATH = .. /或其他内容
好的,所以深入研究它,我刚刚意识到问题实际上只是导致不再定义process.env.RAZZLE_CHUNKS_MANIFEST
😅。
我使用它的唯一目的是检测可缓存的资产,因此看起来我应该能够为您链接的新ManifestPlugin
配置提供立即替换它的权限🎉。
好的!
我已经在我的项目中制作了一个自定义插件,该插件目前看来对于我的用例来说已经足够好了。 以此为起点,您想到的代码非常有帮助。
我已经对其进行了相当大的更改,但是仅供参考,我认为它存在一个问题,即它认为所有内容均由file-loader
因为它使用Array.prototype.every()
而不是Array.prototype.some()
: !webpackOptions.fileLoaderExclude.every(re=>re.test(file.path))
如果在这里分享很有用:
function modifyWebpackConfig({
env: { target, dev },
webpackConfig,
webpackObject,
options: { pluginOptions, razzleOptions, webpackOptions },
paths,
}) {
// TODO: allow passing in extra file categorizers with `pluginOptions`
const fileCategorizers = [
{
test: webpackOptions.urlLoaderTest,
outputName: webpackOptions.urlLoaderOutputName,
},
{
test: webpackOptions.cssTest,
outputName: webpackOptions.cssOutputFilename,
},
{
test: webpackOptions.jsTest,
outputName: webpackOptions.jsOutputFilename,
},
{
exclude: webpackOptions.fileLoaderExclude,
outputName: webpackOptions.fileLoaderOutputName,
},
];
const fileName = path.join(paths.appBuild, "cacheable-assets.json");
const assetPlugin = new WebpackManifestPlugin({
fileName,
writeToFileEmit: true,
generate: (seed, files) => {
const notHashedFiles = new Set();
const hashedFiles = new Set();
const setFileAs = (file, { containsHash }) => {
if (containsHash) {
hashedFiles.add(file);
} else {
notHashedFiles.add(file);
}
};
files.forEach((file) => {
if (file.name.startsWith("..")) {
// Files that start with ".." will live outside of the public/
// folder and therefore can't/shouldn't be accessed.
return;
}
const fileCategorized = fileCategorizers.some(
({ test, exclude, outputName }) => {
const passesTest =
test != null ? fileMatchesAnyRegexp(file, test) : true;
const passesExclude =
exclude != null ? !fileMatchesAnyRegexp(file, exclude) : true;
const fileMatches =
passesTest &&
passesExclude &&
fileMatchesTemplate(file.path, outputName);
if (fileMatches) {
const containsHash = webpackLoaderOutputContainsHash(
outputName,
file,
);
setFileAs(file, { containsHash });
}
return fileMatches;
},
);
if (!fileCategorized) {
// TODO: allow "strict" vs "lazy" mode here where we can only use
// regex on the filename to guess if a file contains a hash in it.
setFileAs(file, { containsHash: false });
}
});
const mutable = [...notHashedFiles].map((file) => file.path);
const immutable = [...hashedFiles].map((file) => file.path);
return {
mutable,
immutable,
};
},
});
if (target === "web") {
webpackConfig.plugins.push(assetPlugin);
}
if (target === "node") {
// NOTE: adding multiple DefinePlugin's causes issues
// so we have to find and edit the existing one.
const definePlugin = webpackConfig.plugins.find(
(p) => p.constructor.name === "DefinePlugin",
);
definePlugin.definitions[
"process.env.RAZZLE_PLUGIN_CACHEABLE_ASSETS"
] = JSON.stringify(fileName);
}
return webpackConfig;
}
const cacheableAssetsPlugin = {
modifyWebpackConfig,
};
或者可以在这里查看https://github.com/bootleg-rust/sites/pull/2/files#diff -59ee436c0396a1f925f067b7e7cbcdee354003236a279e0a87cf8831c7f587e3
嗯,是的,谢谢。 我仍然习惯于新的插件挂钩,我喜欢它!
我认为我仍然无法解决的唯一主要问题是,由于某种原因,在使用razzle start
开发人员模式下运行时, scss
插件/加载器无法正常工作razzle start
但如果我做完整的razzle build
,一切似乎都很好。
任何想法可能是什么? 还是值得将其放在其他地方的github问题上?
还可以将ModifyPaths用于自定义路径,以便可以进行组合。
不行怎么办?
可能是一个新问题.. :)
没关系,Sass加载程序无法正常工作并不是所有关于Razzle的东西。 与版本不匹配有关或与react-scripts
和/或我在提升部门的同级包装中的故事书有关的内容。
添加了用于资产处理的挂钩,关闭closing
我看到您添加了一个外部插件。 我仍然需要为客户端/服务器/无服务器解决此问题。 在金丝雀中对此有任何想法吗? 有点卡住了。
您现在使用的钩子就是这样。
我看到您添加了一个外部插件。 我仍然需要为客户端/服务器/无服务器解决此问题。 在金丝雀中对此有任何想法吗? 有点卡住了。
我肯定发现默认将所有node_modules
捆绑到build/server.js
非常方便(主要在服务器上)。 能够从我的生产docker映像中完全排除node_modules
文件夹似乎太好了。
话虽如此,我不需要使用/测试它如何与任何本机/特定于平台的依赖项一起工作(我感觉像imagemagick这样的事情会出现问题)
我使用“外部”插件所做的一般思考过程是:
const externalsPluginOptions = {
// By default the NodeJS process uses the externals function razzle has and relies on `node_modules` to still be available
// after performing a razzle build. Setting this to `true` would mean that all dependencies attempt to get bundled into
// the build/ output unless explicitly specified as an external
resetNodeExternals: false,
// This probably wouldn't actually be required because the browser runtime
// doesn't have externals by default (i'm assuming)
resetWebExternals: true,
webExternals: [
// add externals for the web runtime here
],
nodeExternals: [
// add externals for the node runtime here
],
};
老实说,在为此选择一个“适当的”配置API之前(特别是如果它要成为令人眼花core乱的内核),我可能不得不以externals
阅读Webpack文档, external不同的用例😅。
目前,我实际上只使用它来将外部设备重置为空,以便将所有内容捆绑到一个易于移植的应用程序中,该应用程序在运行时不依赖于node_modules
最有用的评论
现在发布了金丝雀:)