You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a proposal for a new maps (or whatever chosen name) property for plugins. If implemented in its entirety, I believe it would 'naturally' resolve the virtual files (as in a loader returning files that can't be directly mapped, not files()) issue while maintaining how Snowpack presently operates.
The idea is for a plugin configuration that can strictly map an input to multiple outputs. It works best with examples:
constpluginBasicExt={name: 'map-proposal',maps: [{from: '.jpg',to: ['-400w.jpg','-768w.jpg'],// because Snowpack's default `load()` cannot return a destination, it has to be done like this// load(opts) => Promise<string | { contents: string, map?: string }>asyncload({ fromPath, fromExt, toPath, toExt }){// e.g. process file depending on `toExt` typeif(toExt==='-768w.jpg'){/* ... */}// ...return'data'// or, { contents: data, map: sourcemap }}}]}// it's an Array, so you can have more than one mapping:constpluginBasicExtMultiple={name: 'map-proposal',maps: [{from: '.jpg',to: ['-400w.jpg','-768w.jpg'],asyncload(){}},{from: '.webp',to: ['-400w.webp','-768w.webp'],asyncload(){}}]}
Notice the return type for load() - it only returns the contents. This is because our map told Snowpack it can safely expectto files, and can query the loader for the expected file's data. It doesn't need to keep track of what the plugin has emitted, because it knows what the plugin is going to emit.
In other words, maps is like resolve, but explicitly strict and reversible.
Depending on the application, this syntax may be too simplistic. Here is an alternative pattern, which uses a function for to:
constpluginDynamicNames={name: 'map-proposal',maps: [{from: '.jpg',to: async({ fromPath, fromName })=>{constfileSizes=getFileSizes(fromPath)// `fileSizes` may be a list of sizes that change depending on the src// but, that's fine, you map them into this return object to inform Snowpack what will be generatedreturn[fromName+'-400w.jpg',fromName+'-768w.jpg']},// same as before, but <from|to>Ext -> <from|to>Nameasyncload({ fromPath, fromName, toPath, toName }){/* ... */}}]}
With this version, Snowpack would need to run the to() function on every from file before dev/start. This probably would come with some slowdown, but since the function just needs to return resultant file paths, it should be ok.
The benefit of this pattern is that it lets you arbitrarily map any fromName -> toName. The previous pattern would make Snowpack expect a -400w.jpg and -768w.jpg from every .jpg, no matter what. However, this version lets you decide whether or not you want to do that, perhaps by parsing some info from the source file before returning the mapped file types.
maps allows Snowpack to treat 'virtual files' like it treats resolve outputs. It can resolve a file it doesn't have by tracing it back to a mapsload() function, like it does with resolve. This file could then, like any other file, be processed by additional Snowpack plugins.
Here is my last variant:
constpluginDynamicLoaders={name: 'map-proposal',maps: [{from: '.jpg',to: async({ fromPath, fromName })=>{constfileSizes=getFileSizes(fromPath)// now we can create a loader for each output file// depending on the application this may be more or less desirable// some loaders may have to build the whole "in -> out" chain at once, and can't do thisreturn{[fromName+'-400w.jpg']: async({ fromPath })=>{/* ... */},[fromName+'-768w.jpg']: async({ fromPath })=>{/* ... */},}}}]}
This version would be very powerful, to say the least. It allows you to arbitrarily map dynamic outputs to dynamic loaders. The previous variant and this one would be awesome to have.
This may be a tad out of scope, but here is an interesting extension of this pattern:
constpluginMultipleInputs={name: 'map-proposal',resolve: {input: ['.styl'],output: ['.css']},maps: [{// tells Snowpack that the returned file paths are partials of the `filePath` filefrom: async({ filePath })=>{constrenderer=stylus(awaitfs.readFile(filePath,{encoding: 'utf-8'}))// returns every dep. for the specified Stylus file, using relative pathsreturnrenderer.deps()}}],asyncload({ filePath }){constrenderer=stylus(awaitfs.readFile(filePath,{encoding: 'utf-8'})).set('filename',filePath)returnawaitnewPromise(resolve=>renderer.render((err,css)=>resolve(css)))}}
This pattern makes it very easy to rapidly support loaders that transform multiple partials into an output, as you can see with how small this Stylus plugin would be. This pattern does have annoying issue, though: what exactly is the criteria that determines if a from file should be emitted into the build directory? One view of it could be that the from field is strictly a list of partials, and are not to be emitted. Another field, perhaps deps, could list external dependencies of the file allowing for Snowpack to automatically mark changes from other files without flagging them as partials.
Anyways, back to the meat of the proposal, with a few things left to note:
Something that hasn't been shown is changing the file structure, but this could be done with prefixed path resolving, e.g. to: ['dir/-nest.jpg', '../-breakout.jpg', '/-atroot.jpg]`
What about from: ['.ext1', '.ext2']? Well, my gut says that means "any of", meaning ['.js', '.ts'] would force Snowpack to search for sources of both types when computing their to file paths.
A void return inside of a to() function could exclude that file from that particular mapping, like when you return void in load() currently.
An alternative map-only load() function could be used that does support returning destinations, and the map informs Snowpack what that load function will return. This would be cleaner than calling load() for every requested file.
A reason for hesitation could be that this simply over-complicates Snowpack, and introduces a syntax it may have been attempting to avoid. I think that this feature instead serves as a excellent backup for when the simpler resolveload() pattern is inadequate. It serves as a stricter, more primal form of resolve that does not have the automatic assumptions that Snowpack makes for you, which are sometimes limiting. For example, you cannot have one file that results in multiple of a singular type being emitted (afaik). Without this restriction, you could have plugins that, for example, decompose a terse configuration file in a directory into a component on build, while still working perfectly in dev.
I think something like this should be seriously considered. It could allow for richer optimization, bundling, etc. plugins to be implemented at the "resolving" plugin level. Unfortunately, I don't feel as if I have the technical knowledge to implement something like this, so it would be up to the maintainers to figure out how to construct this proposal.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is a proposal for a new
maps
(or whatever chosen name) property for plugins. If implemented in its entirety, I believe it would 'naturally' resolve the virtual files (as in a loader returning files that can't be directly mapped, notfiles()
) issue while maintaining how Snowpack presently operates.The idea is for a plugin configuration that can strictly map an input to multiple outputs. It works best with examples:
Notice the return type for
load()
- it only returns the contents. This is because our map told Snowpack it can safely expectto
files, and can query the loader for the expected file's data. It doesn't need to keep track of what the plugin has emitted, because it knows what the plugin is going to emit.In other words,
maps
is likeresolve
, but explicitly strict and reversible.Depending on the application, this syntax may be too simplistic. Here is an alternative pattern, which uses a function for
to
:With this version, Snowpack would need to run the
to()
function on everyfrom
file before dev/start. This probably would come with some slowdown, but since the function just needs to return resultant file paths, it should be ok.The benefit of this pattern is that it lets you arbitrarily map any
fromName -> toName
. The previous pattern would make Snowpack expect a-400w.jpg
and-768w.jpg
from every.jpg
, no matter what. However, this version lets you decide whether or not you want to do that, perhaps by parsing some info from the source file before returning the mapped file types.maps
allows Snowpack to treat 'virtual files' like it treatsresolve
outputs. It can resolve a file it doesn't have by tracing it back to amaps
load()
function, like it does withresolve
. This file could then, like any other file, be processed by additional Snowpack plugins.Here is my last variant:
This version would be very powerful, to say the least. It allows you to arbitrarily map dynamic outputs to dynamic loaders. The previous variant and this one would be awesome to have.
This may be a tad out of scope, but here is an interesting extension of this pattern:
This pattern makes it very easy to rapidly support loaders that transform multiple partials into an output, as you can see with how small this Stylus plugin would be. This pattern does have annoying issue, though: what exactly is the criteria that determines if a
from
file should be emitted into the build directory? One view of it could be that thefrom
field is strictly a list of partials, and are not to be emitted. Another field, perhapsdeps
, could list external dependencies of the file allowing for Snowpack to automatically mark changes from other files without flagging them as partials.Anyways, back to the meat of the proposal, with a few things left to note:
to: ['dir/-nest.jpg', '../-breakout.jpg', '/-atroot.jpg
]`from: ['.ext1', '.ext2']
? Well, my gut says that means "any of", meaning['.js', '.ts']
would force Snowpack to search for sources of both types when computing theirto
file paths.to()
function could exclude that file from that particular mapping, like when you return void inload()
currently.load()
function could be used that does support returning destinations, and the map informs Snowpack what thatload
function will return. This would be cleaner than callingload()
for every requested file.A reason for hesitation could be that this simply over-complicates Snowpack, and introduces a syntax it may have been attempting to avoid. I think that this feature instead serves as a excellent backup for when the simpler
resolve
load()
pattern is inadequate. It serves as a stricter, more primal form ofresolve
that does not have the automatic assumptions that Snowpack makes for you, which are sometimes limiting. For example, you cannot have one file that results in multiple of a singular type being emitted (afaik). Without this restriction, you could have plugins that, for example, decompose a terse configuration file in a directory into a component on build, while still working perfectly in dev.I think something like this should be seriously considered. It could allow for richer optimization, bundling, etc. plugins to be implemented at the "resolving" plugin level. Unfortunately, I don't feel as if I have the technical knowledge to implement something like this, so it would be up to the maintainers to figure out how to construct this proposal.
Beta Was this translation helpful? Give feedback.
All reactions