bazelbuild / rules_postcss

PostCSS rules for Bazel
Apache License 2.0
10 stars 13 forks source link

Support execution-time entry points in `postcss_multi_binary()` #63

Open dgp1130 opened 3 years ago

dgp1130 commented 3 years ago

In postcss_multi_binary(), srcs is defined as a label_list() which requires individual files to be listed. This means that in order to process a CSS file, it needs to be known at analysis-time, which is not always feasible. Sometimes the full list of CSS entry points in a build is not known until execution-time. https://github.com/dgp1130/rules_prerender/issues/27 is one such example (I can elaborate more on the use case if that is helpful, but TL;DR: I can't know all the CSS entry points of a build until execution-time). Without knowing all the CSS entry points up front, postcss_multi_binary() is unusable for such use cases.

Ideally, it would be awesome if postcss_multi_binary() srcs would accept a directory of CSS files and process all of them, outputting another directory of processed files at the same relative paths. With this scheme, a tool could output any number of CSS files and reliably process all of them with postcss_multi_binary(). The Bazel side of this would be relatively straightforward, but I'm not familiar with the PostCSS binary or how hard it would be to make it process files in this manner.

I could probably make a PR with a more specific proposal, but this is a pretty specific use case, so I wanted to bring it up for discussion first. Are there any thoughts / concerns about support such a use case or any other constraints I might not be considering?

dgp1130 commented 3 years ago

This issue came up again in rules_prerender under a different context in https://github.com/dgp1130/rules_prerender/issues/41#issuecomment-927228054. Much like the previous use case, I'm trying to process some CSS files, but I can't know which ones until execution-time. postcss_multi_binary() requires its inputs to be given in srcs, which means any entry points must be known at least at analysis-time, which I'm not able to do in this case. It also requires execution-time outputs, which runs into #64. This significantly impacts rules_prerender's ability to use CSS files in declarative shadow DOM, so I would love to find a way forward here.

Is there any interest or desire in rules_postcss to support execution-time inputs and outputs? I'm not 100% on the design, but I think we could update srcs to accept tree artifacts and then add an additional_outputs_dir which declares its own tree artifact as an output. Then we just need some means of configuring plugins to output to the additional outputs directory. It would probably look something like:

load("@npm//@bazel/postcss:index.bzl", "postcss_multi_binary", "postcss_plugin")
load(":my_app.bzl", "some_tree_generating_rule")

# Generates a tree artifact with:
# /foo/bar.css
# /foo/baz.css
# /hello/world.css
some_tree_generating_rule(
    name = "my_tree",
)

# Generates `:styles_outputs`, which is a tree artifact which contains CSS files at
# the same relative paths as the inputs, but processed by the provided plugins.
# /foo/bar.css
# /foo/baz.css
# /hello/world.css
postcss_multi_binary(
    name = "styles",
    srcs = [":my_tree"],
    additional_outputs_dir = "styles_outputs",
    plugins = {
        ":my_plugin": """[{
            // Tell any plugins to output to the `additional_outputs_dir`.
            outputDir: "styles_outputs/",
        }]""",
    },
)

postcss_plugin(
    name = "my_plugin",
    node_require = "my-plugin",
    deps = ["@npm//my-plugin"],
)

I'm not sure how hard this would be to implement since the current Starlark implementation seems to use a different action for each source file, which wouldn't be viable with this scheme. Either postcss needs to support multiple entry points in a single execution (I'm not too familiar with it, maybe it does?) or we'll need a wrapper nodejs_binary() which invokes postcss as a subprocess.

Does such a design seem viable / worth merging? If this is something you would accept, I can take a pass at an implementation and see how well it works. I just want to make sure this is within the scope of the project and has a reasonable chance of landing assuming we can iron out the implementation details.