-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Import query params #3477
Comments
I think we shouldn't encourage publishing packages with this (currently) non standard code, at least in the beginning. |
I think we should only support query params for specifiers that are URLs, so for JS, only ESM. CSS and HTML are all URLs, but langs like SASS and LESS may not be. Some research required. CJS shouldn't be supported -
Agreed with @mischnic.
I think it should only apply to the first pipeline, and should be stripped off afterward. |
I think in order to make the config easiest to use, we shouldn't require escaping the query params. We can build a custom matcher, that strips off the query param first and matches the glob on the left side, and then checks the query params separately if it matches. |
It would also be pretty nice if config like this worked: {
"transforms": {
"*.png": ["parcel-transformer-png"],
"**?as=url": ["parcel-transformer-js-url"],
"**?as=base64": ["parcel-transformer-js-base64"]
}
} Currently, that wouldn't work - for a PNG file, only the {
"transforms": {
"**?as=url": ["...", "parcel-transformer-js-url"],
"**?as=base64": ["...", "parcel-transformer-js-base64"],
"*.png": ["parcel-transformer-png"]
}
} The Any ideas on how we could make this work? |
One alternative is to expose a Parcel API for conveniently allowing transformations to write files to disk. This way it's still a single process with a single command and single file watcher, but there's zero "magic" in the JS module bundling phase as only JS files are importing other JS files (as opposed to arbitrary file types). This seems the most bundler agnostic solution, as any other bundler could pick up these plain JS files. One huge advantage to on-disk code generation is generated code can be statically typed. Type checkers are not going to be able to understand query parameters. However, the transformer could output a file on disk that any type checker will be able to understand. The main potential drawback I can see is passing configuration at the import site might not be possible (as the files would have to exist already) -- so the config would have to be centralized. This might not necessarily be bad, though. The other drawback is potentially extra work compiling files before they are used. However, this might be avoided using conventions around filenames where transformed files are only generated on-demand. |
My opinions here may not fit with what Parcel is trying to achieve, so feel free to ignore. I don't think build systems should hide. If a file is to be transformed in an uncommon way, it should be obvious from the import that something special is happening. import text from './whatever.js?as=text'; The above doesn't really look special. It's just a regular URL. If the above is passed to a system that doesn't recognise the 'as' query parameter, it'll interpret the module as JavaScript, which feels like a bad fallback. If the transform doesn't have an 'acceptable' fallback (if a file fails to minify, the result still works), the import should fail as soon as possible. Other ideas: import text from 'as-text:./whatever.js'; The import text from './whatever.js/text'; This treats
I'm not sure if files within Eg: import text from 'whatever-package?as=text'; |
That's interesting feedback @jakearchibald! One reason we were thinking about query parameters was to avoid making If we went with something more specific like a URL protocol, it would also affect resolution in addition to transformation and would need to become a bit more integral to Parcel core. But I can see your point about fallback in case a build system isn't configured to use As for the specific suggestions, protocols seem like an interesting idea, but feel a bit like a "source" rather than a transform. e.g. normal protocols like |
There's a good point made that "falling back" to JavaScript when A couple constraints:
I was in favor of query parameters before, but @jakearchibald made a good point that unknown query parameters rarely change how a route is resolved by a server. Right now I'm leaning more towards the additional extension: import "./file.js.txt" This has the benefit of reusing existing configuration: {
"*.{txt,text}": ["@parcel/transformer-raw"]
} The problem comes in trying to resolve it. How do you decide The answer could be |
The only exception I can think of is
Yeah, that could work. Would it be chainable? Say I had two transforms:
…would I be able to do: import data from "./file.png.img-opt.img-data"; |
For that specific case I don’t think we’d encourage the use of optimizers via file extension vs Parcel’s config format: {
"transforms": {...},
"optimizers": {
"*.png": ["@parcel/optimizer-pngmin"]
}
} However for the generic case of chaining I’m a little bit torn. There is some precedence for this in build systems as well, Ruby on Rails’ asset pipeline uses extensions to turn on different file transforms (although that’s at the file system level, not at import-time). We’d have to consider how the Parcel pipeline interacts with this: import "./img.png.img-data" If the config were to look something like this: {
"transforms": {
"*.png": ["@parcel/transform-png"],
"*.img-data": ["@parcel/transform-img-data"],
},
"optimizers": {
"*.png": ["@parcel/optimizer-pngmin"]
}
} There’s a lot of questions about which plugins run and when they run:
I’m not sure we can come up with a pipeline algorithm that will work for all cases. In which case I’d prefer forcing users to do a little more work to be explicit than to have them be frustrated with a bad abstraction. |
Vague late night thoughts:
Some ideas on syntaxes other than import "viewer~specifier"
import "viewer^specifier"
import "viewer|specifier" Exampleimport "image-data~./file.png" // runs optimizer-png, but not transform-png
import "raw-source~http://example.com/script.js" // doesn't run any optimizers or transforms {
"transforms": {
"*.js": ["@parcel/transform-babel"],
"*.png": ["@parcel/transform-png"]
},
"optimizers": {
"*.js": ["@parcel/transform-uglify"],
"*.png": ["@parcel/optimizer-pngmin"]
},
"viewers": {
"image-data": ["@parcel/viewer-image-data"],
"raw-source": ["@parcel/viewer-raw-source"]
}
} |
Just a random thought. Personally, I dislike having to change my import statements because of a bundler. Even the query parameter is annoying, because it feels like I'm mixing the concern of loading modules with the concern of bundling code. They're related, but should be more explicitly separate, imho. Combined with the concern that no all languages support urls for their module imports, the solution seems pretty clear:
Here's the same examples from the OP: import MyComponent from './MyComponent.js';
// bundler: { as: 'raw-text' }
import MyComponentSrc from './MyComponent.js';
// bundler: { as: 'react-types' }
import MyComponentPropTypes from './MyComponent.js'; Simple, easily understood, not bundler specific, and supported in all languages with comments. (Syntax obviously up for debate, that was with about 10 seconds of consideration.) |
I still think query params are by far the best option here, even with the concerns raised.
The fallback issue when switching tools is valid, but honestly not that bad I think. You'll already have other problems in anything complex. There are many cases where behavior of imports relies on specific configuration. I think we should try to keep this as simple as possible and not try to make Parcel even more complicated to support this one feature. Query params can be widely used for many purposes, including changing output type or passing other options (e.g. image resizing). Supporting them is simple, and opens up the doors to many features without much added complexity. |
I strongly disagree with that. We already should be supporting query parameters for other purposes, if someone specifies a file By assigning a query parameter, we're changing the meaning of query parameters. What if someone actually means to specify a Not to mention that using an existing standard would be harder to unwind than something non-standard. Failing early is far better than failing softly or silently. Making it easier for users to debug ahead of time. It costs us nothing to have the syntax be something other than query parameters. Having it be query parameters does come with a cost.
It wouldn't try to do any of those things, unless you're talking about Parcel in which case it also should not do any of those things, it should fail the same way it's meant to fail: import "./image.png"
// TypeError: 'image/png' is not a valid JavaScript MIME type.
import "./file.js?as=raw-source"
// No Error Module loading is incredibly complicated today because of a whole bunch of early decisions that were not all that well thought out. Having been one of the people (on Babel) that helped make decisions that directly led to the ecosystem having a ton of "problem code" today, I really wish we had done things differently. There are a lot of things Parcel can make short term decisions with, but one of them should not be module loading or user code, because it sticks around for a very long time.
There is a bigger problem here than parameters to transformers though. Something like There are two problems: A. Passing parameters to transformers. I agree that both need to be solved, but:
If you solve A before you solve B, you will have to support two things. But if you solve B and A at the same time, you can have a unified solution. Not to mention that query parameters decentralize your pipeline from out of your config file and plugins and into your code. |
I don't think it's changing the meaning at all. So, if we add another syntax for this, we'd effectively be supporting two ways to do the same thing. |
But that is changing the meaning. Query parameters, and module specifiers in general only affect the way a module is resolved. There is currently no standard for transformations and if one existed it would not be based on query params because it would be a backwards-incompatible change. The only question you need to ask is “If I removed Parcel from the equation would this fail immediately or potentially quietly/silently.”
That’s not how the pipeline config works though. If I import The only alternative I can come up with is specifying raw transformer to match
People are always going to find ways to use Parcel in ways we did not intend or want them to. There are dozens of Babel plugins that I wish didn’t exist. But that’s an argument for being more restrictive and provide more guidelines. Letting people hack away in whatever way they please is only going to make it harder to make changes to Parcel in the future. |
Something came up at TPAC that feels relevant, but sorry if this is just noise: import obj from 'https://example.com/whatever.json'; There's a proposal to make this work in browsers, where It wasn't resolved at TPAC, but to me it feels like the importer should have primary control over how the resource is processed, not the server. Something in the import statement needs to say "treat this as JSON", and it can't be a syntax that, if JSON imports are unsupported, falls back to "treat this as JS".
It comes down to whether build tools should behave like JS or CSS. In JS, if you call an unrecognised function, it throws. In CSS, if you set an unrecognised property, it's a no-op. |
Servers can implement an |
If you fetch that URL it does resolve to an asset, the server might be behind the scenes “transforming” that, but from the client you are only aware of resolution — and that resolution is important for the client for caching.
Except Parcel and the server can conflict. If my server wants
Exactly, and it will probably break quietly or silently which is why well designed APIs will intentionally make it loudly fail when someone passes a deprecated/removed query param. We don’t have the ability to do that, if someone uses a module relying on Parcel-specific query params outside of Parcel it’s going to fail quietly or silently. If they use something like |
Yup, and they'll likely have no recourse but to change their code, thus making it incompatible between tools. At least with query params there is some chance of other tools implementing them. I really don't want to be in the business of inventing syntax with Parcel. |
FWIW, there was a discussion in create-react-app a while back about this: facebook/create-react-app#3722. @gaearon made a proposal to use named imports for this. Not sure where it ended up, but appears that query params were also proposed as an option in the thread. Would be useful to get some buy in from other tools for whatever we move forward with. |
Other tools could also implement a There’s still always the possibility that a standards body will want to get involved here, there’s been talks about having “loaders” for years now. By designing something more robust we can have more influence on the design from standards bodies. But if we design something with obvious flaws, then it will be discarded. |
I am sort of intrigued by the idea of protocol again with the combination of viewers. Using a protocol like
It would be nice to also be able to select if optimizers are ran or not. |
FYI I implemented "named pipelines" in #3613 using a protocol-like syntax. It's not implemented in import specifiers yet, only in |
Named pipelines are implemented in import specifiers now. Query params for options are not yet implemented. |
I think it will be good to use a single syntax for the whole JS ecosystem so I suggest combining different solutions into one - tc39/proposal-import-attributes#22 |
Problem Statement
Currently in Parcel v2, dependencies are resolved to files and the resulting file paths are matched against the configured globs in the
.parcelrc
file to see which pipeline the file should be transformed by. This works for most cases, but sometimes you might want to force a dependency down a specific pipeline. For instance, let's say you are building documentation for a React component. By default, importing the React component will bundle the component as a JavaScript module, but what if you additionaly want the component's source as plain text? Maybe you also want to run some transformer over the component that extracts what prop types it expects and bundle that as well.A very naive solution that would work for any and all tooling would be to just create files for the source text and prop types in a prebuild step and then you can import three individual files. The downside to this approach is that you would then have two build steps, which means you have two build commands that you have to string together and two separate watchers if you want to build in watch mode. Imagine in this scenario you updated
MyComponent.js
. Both the prebuild and the bundle watchers would pick up the change and start rebuilding. Then when the prebuild step finishes, the derived files are updated, which causes the bundler to rebuild yet again. It makes for a much better developer experience if this can all be handled in one process.With webpack, this is usually solved by using inline loaders.
One downside to this approach is that the detail that you are using webpack leaks into your source code. Ideally this could be done in a more bundler agnostic way.
Proposed Solution: Import Query Params
The ECMAScript modules standard (ESM for short) is now supported across all modern browsers and will soon be supported by Node.js as well. One neat thing about ESM is that it essentially treats all module specifiers as URLs, which means adding query parameters is perfectly valid syntax and not something bespoke to a specific bundler. This seems like a great way to specify how you would like a dependency loaded:
So what would it take for Parcel to support this? The most straightforward approach requires no changes to Parcel's configuration so it's got that going for it. All you would have to do to force a file to be transformed by a different pipeline is add a query param to the module specifier and configure a pipeline in your
.parcelrc
with a glob that matches it.The only change we would need to make in Parcel would be to slice off the query params from the module specifiers to resolve the file and then tack them back on to resolve the transformation pipeline. Perhaps it would be nice to have a special glob matcher that makes it easier to write globs of this type. Query params could also be passed on to transformers as well.
Drawbacks
as=url
from a JavaScript file vs a CSS file. This could be solved by using a more specific parameter likeas=js-url
, but the problem still remains for imports without query parameters. If we decided to change the configuration to allow for this type of thing then it could potentially lead to a cleaner/smaller/simpler config.Alternatives
One alternative would be to make a change to Parcel's configuration. I do like how simple Parcel's configuration is at the moment, but I could imagine this not working perfectly for all cases. I'm not aware of any of these cases at the moment, but that's what the RFC is for. If no one brings up a compelling case that proves that a change to configuration would either be necessary or would be a much cleaner solution, then this seems like a reasonable place to start.
Another alternative could be to use a special extension like extensions leading with a certain number of underscores.
Parcel could split the module specifier like it would have to with query parameters and use the parts before the special extension to resolve the file and tack the special extension back on to resolve the pipeline. You could also have a prebuild step that actually creates these files which means the imports would still work with other tooling. One potential issue is that the special extension might conflict with legitimate extensions.
Open Questions
node_modules
or only source code?cc @devongovett @wbinnssmith @jamiebuilds
The text was updated successfully, but these errors were encountered: