How defineDynamicResource Simplifies Web Accessible Resources in CRXJS Chrome Extensions
The defineDynamicResource function exported from packages/vite-plugin/src/node/defineManifest.ts creates a type-safe placeholder entry for dynamic web accessible resources that the CRXJS Vite plugin automatically resolves to actual bundled file names during the build process.
The defineDynamicResource helper in the crxjs/chrome-extension-tools repository eliminates manual enumeration of script hashes in Chrome Extension Manifest V3 files. This function abstracts the boilerplate required to declare resources that will be injected at runtime, while the build system handles the concrete file name resolution and cross-browser compatibility.
How defineDynamicResource Works in the Build Pipeline
Creating the Placeholder Entry
When invoked, defineDynamicResource returns a manifest entry object containing three key properties. The matches field specifies URL patterns that may request the resource. The resources array contains a single sentinel value DYNAMIC_RESOURCE (the literal string <dynamic_resource> defined at lines 71-72 of defineManifest.ts). Finally, the use_dynamic_url boolean flag tells Chrome whether to generate a dynamic chrome.runtime.getURL for the resource.
Default Configuration Values
The function provides sensible defaults when options are omitted. If you call it without arguments, it supplies the most permissive match set—http://*/* and https://*/*—and sets use_dynamic_url to false (lines 60-62). This zero-config approach works out-of-the-box while still allowing explicit overrides for specific domains.
Build-Time Processing in plugin-webAccessibleResources.ts
During the renderCrxManifest hook in packages/vite-plugin/src/node/plugin-webAccessibleResources.ts, the plugin scans the user's web_accessible_resources array for the DYNAMIC_RESOURCE placeholder. When detected, the plugin executes three critical operations (lines 94-104):
- Strips the placeholder from the declaration to ensure the final manifest never contains the literal string
<dynamic_resource>(lines 98-102). - Collects match patterns into a
SetnameddynamicScriptMatches, aggregating all URLs specified by the developer. - Propagates the flag by storing the
use_dynamic_urlvalue indynamicScriptDynamicUrlfor later application to generated resources (lines 103-104).
Resolving Dynamic Scripts to Real Resources
After Vite emits the build file manifest, the plugin walks through all content-script entries. For each script identified as dynamic (isDynamicScript === true), the plugin assigns the collected dynamicScriptMatches and the use_dynamic_url flag to the generated web accessible resource entry. The placeholder transforms into actual file names—including the bundled script hash and any imported assets—ensuring the runtime can access the correct URLs.
Final Manifest Cleanup
The plugin performs post-processing to ensure valid output. Duplicate web accessible resource entries are merged, and the placeholder is completely removed from the final JSON. For browsers that do not support the use_dynamic_url property (such as Firefox), the plugin strips this field (lines 26-29), maintaining cross-browser compatibility without manual intervention.
Implementing defineDynamicResource in Your Extension
Declaring Dynamic Resources in the Manifest
Define your dynamic web accessible resources in the manifest configuration file using the helper function:
// src/manifest.ts
import { defineManifest, defineDynamicResource } from 'crx/vite-plugin'
export const manifest = defineManifest({
name: 'My Extension',
version: '1.0',
manifest_version: 3,
web_accessible_resources: [
// All dynamic scripts can be loaded from any HTTPS origin
defineDynamicResource({
matches: ['https://example.com/*'],
use_dynamic_url: true,
}),
],
// …other manifest fields…
})
This call produces a web accessible resource entry containing the placeholder <dynamic_resource> and your specified match pattern.
Internal Build Transformation
The plugin transforms your declaration during the build. Here is the simplified internal logic from plugin-webAccessibleResources.ts:
// Internally (simplified from lines 94-104)
for (const r of manifest.web_accessible_resources) {
if (r.resources.includes(DYNAMIC_RESOURCE)) {
// Remove placeholder, collect matches, remember flag
const matches = new Set(r.matches)
const useDynamic = r.use_dynamic_url ?? false
// Later, when Vite emits a dynamic script file:
web_accessible_resources.push({
matches: [...matches],
resources: ['assets/dynamic-script.abc123.js'],
use_dynamic_url: useDynamic,
})
}
}
Accessing Dynamic URLs at Runtime
When use_dynamic_url is enabled, Chrome rewrites chrome.runtime.getURL to return temporary URLs that remain valid even when script hashes change:
// content script (dynamic)
import scriptUrl from './dynamic-script?script'
// Chrome resolves it to the actual bundled file
chrome.runtime.getURL(scriptUrl).then(url => {
const s = document.createElement('script')
s.src = url
document.head.append(s)
})
Summary
defineDynamicResourcecreates a placeholder entry inpackages/vite-plugin/src/node/defineManifest.tswith the sentinel valueDYNAMIC_RESOURCE(<dynamic_resource>), match patterns, and theuse_dynamic_urlflag.- The default configuration provides permissive URL matches (
http://*/*,https://*/*) and disables dynamic URLs by default for immediate usability. - During the
renderCrxManifesthook inplugin-webAccessibleResources.ts, the plugin strips the placeholder, aggregates matches intodynamicScriptMatches, and preserves theuse_dynamic_urlsetting. - The system automatically resolves placeholder entries to actual bundled file names (including content hashes) for all scripts marked with
isDynamicScript === true. - Cross-browser compatibility is handled by stripping
use_dynamic_urlfor unsupported browsers like Firefox (lines 26-29).
Frequently Asked Questions
What is the DYNAMIC_RESOURCE sentinel value?
The DYNAMIC_RESOURCE constant is a literal string value <dynamic_resource> defined in packages/vite-plugin/src/node/defineManifest.ts at lines 71-72. It serves as a marker that the plugin-webAccessibleResources.ts build hook detects and replaces with actual file names during the Vite build process. This sentinel allows developers to declare "any script that will be injected at runtime" without knowing the final hashed file names in advance.
Does defineDynamicResource work with Firefox?
Yes, but with compatibility adjustments. The use_dynamic_url flag is specific to Chrome's Manifest V3 implementation. During final manifest cleanup in plugin-webAccessibleResources.ts (lines 26-29), the plugin detects Firefox and strips the use_dynamic_url property from the output. The dynamic resource handling itself works across browsers, but the dynamic URL generation feature is Chrome-specific.
How does use_dynamic_url affect chrome.runtime.getURL?
When use_dynamic_url is set to true, Chrome generates a dynamic URL via chrome.runtime.getURL that remains stable even when the underlying file hash changes between builds. This allows content scripts to inject sub-resources using chrome.runtime.getURL without hardcoding specific asset hashes. The CRXJS plugin propagates this flag from your defineDynamicResource call to the final generated web accessible resource entries.
Where does the actual file name substitution happen?
The substitution occurs in packages/vite-plugin/src/node/plugin-webAccessibleResources.ts during the renderCrxManifest hook. The plugin scans for entries containing DYNAMIC_RESOURCE (lines 94-103), removes the placeholder, and later assigns actual file names (like assets/dynamic-script.abc123.js) when processing content scripts where isDynamicScript === true. This happens after Vite has generated the final asset hashes but before the manifest is written to disk.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →