-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
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.
import MyComponent from './MyComponent';
import MyComponentSrc from '!!raw-loader!./MyComponent';
import MyComponentPropTypes from `!!extract-react-types-loader!./MyComponent`;
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:
import MyComponent from './MyComponent.js';
import MyComponentSrc from './MyComponent.js?as=raw-text';
import MyComponentPropTypes from './MyComponent.js?as=react-types';
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.
{
"transforms": {
"**/*.js\?as=react-types": ["parcel-transformer-extract-react-types"]
}
}
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.
import profilePic from './profile.png?width=300&height=300';
Drawbacks
- Since this solution is based on ESM it makes it pretty JavaScript centric. Some other languages and module formats support module specifiers as URLs, but what about the ones that don't? Parcel will either end up encouraging developers to write non standard code that only works because it's run with Parcel or this behavior may not end up being supported in all types of files.
- Text matching can get hairy. It seems possible that this could cause Parcel config files to balloon especially considering there could be multiple query parameters that might influence what pipeline a file should be transformed by.
- This is more of a drawback for the transformation configuration model in general, but one thing it doesn't accommodate is influencing what pipeline to use based on the file that defined the dependency. For instance you might want something different when importing
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.
import MyComponent from './MyComponent.js';
import MyComponentSrc from './MyComponent.js.__raw';
import MyComponentPropTypes from './MyComponent.js.__react-types';
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
- ESM treats module specifiers as URLs but CommonJS does not. Should Parcel only support this feature on module specifiers that are known to be URLs or should it be less strict?
- Should this be supported in
node_modules
or only source code? - What happens when the configured transformer changes the type of the asset? Do we use the query parameters to match the next pipeline or do they only apply to the first pipeline? (Seems like you would probably have to use them to match if you wanted any control over whether to jump to a new pipeline or not)
- Any examples where this type of configuration doesn't cut it?