-
Notifications
You must be signed in to change notification settings - Fork 192
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
Add sourcemap, nesting, and css-modules CLI flags #68
Conversation
|
#[derive(Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct SourceMapJson<'a> { | ||
version: u8, | ||
mappings: String, | ||
sources: &'a Vec<String>, | ||
sources_content: &'a Vec<String>, | ||
names: &'a Vec<String> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the code for actually writing the sourcemap json are pulled almost exactly from node/src/lib.rs
. I didn't pull out a common module, since I'm not totally sure how the project likes to organize the code, but there's a common bit of code to pull out, potentially... maybe into parcel_sourcemap
itself, but I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah Think putting it in parcel_sourcemap
would be good eventually. Doesn't have to happen for this PR though.
let filename = path::Path::new(&cli_args.input_file) | ||
.file_name() | ||
.unwrap() | ||
.to_str() | ||
.unwrap() | ||
.into(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When running the CLI twice on the same file, but with different locations, the input_file arg is different, even if the css source code is the same. That meant there'd be different hashes in for the css module output! This normalizes the filename for the stylesheet, which means the hash is more consistent.
I'm still pretty new to the codebase, so it could be that there's some downstream effects of this that I'm not expecting.
If there's a nicer way to accomplish this in rust, I don't love these unwraps...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should probably avoid this. For example, if you had two different files in your project each named index.css
or something but in different folders, the class names could clash. Using the full absolute path (or a relative one from some project root) avoids this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's a good point, that does seem like it will definitely happen, especially once there's multiple file support!
I guess a pertinent question is whether invoking from different locations, the output should be deterministic. I still think yes (though, it's clear that file_name
doesn't do the job here). Is there something else to hash that makes sense? The source file contents?
let map = if let Some(ref mut source_map) = res.source_map { | ||
source_map.set_source_content(0, &res.code).map_err(|_| io::Error::new(io::ErrorKind::Other, "Error setting sourcemap"))?; | ||
let mut vlq_output: Vec<u8> = Vec::new(); | ||
source_map.write_vlq(&mut vlq_output).map_err(|_| io::Error::new(io::ErrorKind::Other, "Error writing sourcemap vlq"))?; | ||
|
||
let sm = SourceMapJson { | ||
version: 3, | ||
mappings: unsafe { String::from_utf8_unchecked(vlq_output) }, | ||
sources: source_map.get_sources(), | ||
sources_content: source_map.get_sources_content(), | ||
names: source_map.get_names() | ||
}; | ||
|
||
serde_json::to_vec(&sm).ok() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
almost line for line from node/src/lib.rs
, but with different map_err
, since we don't have the CompilerError
enum here
if cli_args.css_modules { | ||
let css_modules_json = serde_json::to_string(&res.exports)?; | ||
println!("{}", css_modules_json); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
printing css modules to stdout feels a little weird, but I guess if you add the --css-modules
flag, you're expecting css modules, so better to get it than not?
Maybe better to require the --output-file
flag in order to turn on --css-modules
. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think requiring output file for css modules makes sense. Same with source maps, unless we inline the whole thing. I guess the main usecase for stdout is to pipe it somewhere, so probably most useful if that only includes the css.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make this switch! Drop the complexity / printing here, and make an output file required for using the css modules flag.
fn infer_css_modules_filename(output_file: Option<String>) -> Result<String, std::io::Error> { | ||
if let Some(file) = output_file { | ||
let path = path::Path::new(&file); | ||
if path.extension() == Some(ffi::OsStr::new("json")) { | ||
Err(io::Error::new(io::ErrorKind::Other, "Cannot infer a css modules json filename, since the output file extension is '.json'")) | ||
} else { | ||
// unwrap: the filename option is a String from clap, so is valid utf-8 | ||
Ok(path.with_extension("json").to_str().unwrap().into()) | ||
} | ||
} else { | ||
Ok("styles.json".into()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on rereading this, I don't actually think we ever infer the filename if we don't have an output file specified; this should take a string and shouldn't have the outer condition
file.write_str( | ||
r#" | ||
.foo { | ||
border: none; | ||
} | ||
"#, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Borrowed test values from src/lib.rs
Skimmed it and it looks great. Thanks for writing tests as well! Will look in more detail tomorrow. |
Does this support sending the output of CSS Modules JSON and CSS to stdout? |
How would that work and what is the usecase for that? How would you separate them? I assume the usecase for stdout is piping to another program, but if we mix CSS and JSON that will be harder... |
Yeah so that is the tricky part. My use case is to call the CLI and pipe it to my app. I'm building a Rails middleware that will intercept requests for CSS files and return the CSS file - basically a proxy. CSS files will be built on demand using the parcel-css CLI. But (at least in development) it is more efficient and faster to return the contents directly from the CLI, as opposed to the CLI writing to a file(s) and my app opening and rendering file contents of the file. Instead, my app calls the CLI and returns exactly what the CLI returned, and/or be able to work with the returned data. So when this is just CSS, all is well. But when we have JSON and CSS - like we do with CSS modules - then it's tricker. I would still prefer not to read/write files. So maybe in this case, we return everything as json: {
"css": ".header_abc123 { color: red; }",
"modules": {
"header": "header_abc123"
}
} This makes it trivial for the caller to work with the returned data. Hope that helps shed some light on what I'm after. thx |
Going to merge this and do a few fixups separately. Thanks again so much for the contribution! |
Yeah, I think piping the css modules json file to stdout is wrong; it's probably better to leave it out unless the output file is specified. |
Oh, @devongovett you already shipped the fixups! Nice! lmk if there's other pieces I can chip in on, I'm starting to get a sense for the codebase, and I like it! |
@rrcobb Thanks! A few things we could add if you're up for any of them:
I'm also currently working on a very basic |
That would awesome 👏 |
Just thinking out loud here, but I have an alternative to outputting the CSS and JSON... I currently use Webpack and mini-css-extract-plugin, which supports CSS modules. It also allows me to configure the format of the class names to module names using the So as an example... I set the .classOne {
color: red;
} I get output like this: .cssm_abcd1234 {
color: red;
} All as you would expect. But of course, in order to actually find out what the resulting module names are, I have to run webpack to get the JSON output of class names to module names. I have a better way :) Because I know how the module names are created, i can actually guess what they are without needing to run webpack. So in Ruby (my language of choice), I can get the module name: css_path = 'path/to/my/css_modules.css'
class_name = 'classOne'
Digest::MD5.hexdigest("#{css_path}\x00#{class_name}")[0, 8] I now know the module name without needing to compile webpack and reading the JSON. Sorry for the slightly long winded description, but what I'm getting at, is could we have a deterministic way of doing the same with parcel-css? This way, all I would need from parcel-css is the CSS output. I certainly don't need as many config options as mini-css-extract-plugin has. In fact, I don't need any. Thoughts? |
Address parts of #52, but not
--targets
or transforming multiple files.--minify
a boolean option, instead of a subcommand--input-file
option as part of the minify subcommand, input file is now a required positional arg--output-file
from subcommand to the top level--nesting
boolean option, to enable nesting--css-modules
and--css-modules-output-file
options--sourcemap
optiontests/cli_integration_tests.rs
CSS Modules and sourcemap options make what seem to me like decent default choices for where to send their output.
out.css
, then the map is written toout.css.map
out.json
if there's an output file namedout.css
I'd be glad for any feedback, and happy to carve it into smaller chunks, in case this PR is too large.