wmr: Userland plugin doesn't work if it uses the the same file extension as a built-in plugin, with a different import prefix
UPDATE: the bugs still exist, but I now have found solutions to work around WMR’s handling of core file extensions, such as SVG and JSON files. Here is a working text:
plugin that does not require using alternative file extensions in order to avoid colliding with WMR’s own handling (e.g. *.jsonld
for JSON or *.xml
for SVG):
/** @type {import('wmr').Plugin} */
function textPlugin() {
const IMPORT_PREFIX = 'text:';
const INTERNAL_PREFIX = `\0${IMPORT_PREFIX}`;
return {
name: 'text-plugin',
async resolveId(id, importer) {
if (id[0] === '\0' || id[0] === '\b') return;
if (id.startsWith(IMPORT_PREFIX)) {
id = id.slice(IMPORT_PREFIX.length);
} else return;
// necessary for SVG file extension
// (otherwise WMR's url: import prefix plugin creates an inline data: URL for the SVG asset)
if (config.mode === 'start') {
id = `${id}${'\0'}`;
}
// just in case the given importer has an import prefix
if (importer) {
importer = importer.replace(/^[\0\b]\w+:/g, '');
}
const resolved = await this.resolve(id, importer, { skipSelf: true });
if (!resolved) {
return;
}
// necessary for JSON file extension
// (because WMR's json: import prefix plugin handles it by default)
if (/^[\0\b]\w+:/g.test(resolved.id)) {
resolved.id = resolved.id.replace(/^[\0\b]\w+:/g, '');
}
return `${INTERNAL_PREFIX}${resolved.id}`;
},
async load(id) {
if (!id.startsWith(INTERNAL_PREFIX)) return;
id = id.slice(INTERNAL_PREFIX.length);
// necessary for SVG file extension
// (otherwise WMR's url: import prefix plugin creates an inline data: URL for the SVG asset)
if (config.mode === 'start') {
id = id.replace(/\0$/, '');
}
id = path.resolve(config.cwd || '.', id);
this.addWatchFile(id);
const s = fs.readFileSync(id, { encoding: 'utf8' });
return `export default \`${s}\``;
},
};
}
textPlugin.enforce = 'pre';
config.plugins.push(textPlugin());
Describe the bug
WMR’s built-in plugins that handle specific file extensions (e.g. SVG in the “URL plugin”) interfere with userland plugins that wish to handle the same file extensions under a different scheme (i.e. custom import prefix, e.g. svg:
to include the raw SVG markup instead of creating an external image URL)
Culprit:
To Reproduce
cake.svg
=>
<!-- https://heroicons.com/ -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 15.546c-.523 0-1.046.151-1.5.454a2.704 2.704 0 01-3 0 2.704 2.704 0 00-3 0 2.704 2.704 0 01-3 0 2.704 2.704 0 00-3 0 2.704 2.704 0 01-3 0 2.701 2.701 0 00-1.5-.454M9 6v2m3-2v2m3-2v2M9 3h.01M12 3h.01M15 3h.01M21 21v-7a2 2 0 00-2-2H5a2 2 0 00-2 2v7h18zm-3-9v-2a2 2 0 00-2-2H8a2 2 0 00-2 2v2h12z"
/>
</svg>
npm init wmr svg-import
cd svg-import
- open
public/pages/_404.js
, add at the topimport svgCakeUrl from './cake.svg'
(which is equivalent toimport svgCakeUrl from 'url:./cake.svg'
), and add in the JSX<img src={svgCakeUrl} alt="" width={48} height={48} />
npm start
(ornpm run build --prerender && npm run serve
) => all works fine, but the image asset remains external, now we want to inline the SVG markup inside the JSX. Next step:- Add at the top
import svgCakeContent from 'svg:./cake.svg'
, and insert the following userland plugin inwmr.config.mjs
:
UPDATE: scroll to the top of this thread to see the most up to date, working text:
plugin.
function svgPlugin() {
const IMPORT_PREFIX = 'svg:';
const INTERNAL_PREFIX = '\0svg:';
const SVGEXT = '.svg';
return {
name: 'svg-plugin',
async resolveId(id, importer) {
if (id[0] === '\0' || id[0] === '\b') return;
if (id.startsWith(IMPORT_PREFIX)) {
id = id.slice(IMPORT_PREFIX.length);
} else if (config.mode === 'build') {
return;
} else if (!id.endsWith(SVGEXT)) {
return;
}
const resolved = await this.resolve(id, importer, { skipSelf: true });
if (!resolved) return;
resolved.id = `${INTERNAL_PREFIX}${resolved.id}`;
return resolved.id;
},
async load(id) {
if (!id.startsWith(INTERNAL_PREFIX)) return;
id = id.slice(INTERNAL_PREFIX.length);
id = path.resolve(config.cwd || '.', id);
this.addWatchFile(id);
const s = fs.readFileSync(id, { encoding: 'utf8' });
console.log(j);
return `export default \`${s}\``;
},
transform(code, id) {
if (!id.endsWith(SVGEXT) || id.startsWith(INTERNAL_PREFIX)) return;
return {
code: `export default \`${code}\``,
map: null,
};
},
};
}
config.plugins.push(svgPlugin());
- Now run
npm start
(ornpm run build --prerender && npm run serve
), and refresh the page => fail. - Copy the file
cake.svg
tocake.svgx
(for example), change the extension accordingly in the above plugin (const SVGEXT = '.svgx'
) and of course in the import as well (import svgCakeContent from 'svg:./cake.svgx'
) => success.
I added a “trace” plugin to console.log()
the resolveId()
etc., and indeed there seems to be some interference due to the common SVG file extension. Related issue: https://github.com/preactjs/wmr/issues/446#issuecomment-802225547
Expected behavior
WMR’s built-in plugins should allow userland plugins to define custom import prefixes for common file extensions.
Desktop (please complete the following information):
- OS: all
- Browser: all
- WMR Version: all
Additional context
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 20 (5 by maintainers)
Follow-up issue: https://github.com/preactjs/wmr/issues/524
Ah, great! I found a trick to work around the JSON file extension (i.e. to bypass WMR’s core
json:
import prefix handler):After the
this.resolve()
call in mytext:
plugin I now “sanitize” the returnedresolve.id
, ensuring no other import prefix was somewhat injected by WMR’s core plugin chain:I’ll update my
text:
plugin code snippet accordingly, in previous comments in this thread.PS: just in case the
importer
itself carries an import prefix, I also sanitize it before callingthis.resolve(id, importer, { skipSelf: true })
:Well done for cutting a new release! 👍 (which I am now using instead of a self-build from the
main
branch)I updated my post in this thread about the ordered list of plugins: https://github.com/preactjs/wmr/issues/449#issuecomment-808466358
By the way, a built-in
text:
import prefix plugin would be nice as a core WMR feature, don’t you think? 😃 That being said, I think it is more important that userland plugins can handle common file extensions without colliding with WMR’s own internal file handling / import logic.