blob: 90372e61ea8c5981bbb319d08f6ba9eaaf6c45a9 [file] [log] [blame]
"""This module defines the sass_library and sass_binary rules."""
load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")
load("@npm//:csso-cli/package_json.bzl", _csso_bin = "bin")
load("@npm//:sass/package_json.bzl", _sass_bin = "bin")
def sass_library(name, srcs, visibility, deps = []):
"""Groups together one or more .scss or .css files.
This rule simply copies its input files to the Bazel output tree with the copy_to_bin rule
(https://docs.aspect.build/rulesets/aspect_bazel_lib/docs/copy_to_bin). The reason we use this
rule rather than a filegroup is that the js_binary rules automatically generated[1] by rules_js
are not designed to work with files outside of the Bazel package where they are defined, and
doing so produces errors such as:
Expected to find file foo.scss in //my/package, but instead it is in //another/package.
[1] https://docs.aspect.build/rulesets/aspect_rules_js/docs/#using-binaries-published-to-npm.
Args:
name: Name of the target.
srcs: Sass source files (either .scss or .css files).
visibility: Visibility of the target.
deps: Any sass_library dependencies.
"""
copy_to_bin(
name = name,
srcs = srcs + deps,
visibility = visibility,
)
def sass_binary(name, srcs, entry_point, out, mode, deps = []):
"""Compiles Sass stylesheets into CSS.
This macro is a simple wrapper around the Sass compiler (https://www.npmjs.com/package/sass).
In addition to compiling to CSS, this macro optimizes the resulting CSS with CSSO
(https://www.npmjs.com/package/csso-cli). This eliminates any repeated rules that might result
from including the same Sass file multiple times.
The mode argument takes one of the following three values:
- "development": Produces an unminified <name>.css file with an embeded sourcemap.
- "production_sourcemap": Produces a minified <name>.css file and a <name>.css.map sourcemap.
- "production": Produces a minified <name>.css file without a sourcemap.
The "production_sourcemap" and "production" modes produce virtually identical bundles; the only
difference is that the former includes a `/*# sourceMappingURL=<name>.css.map */` comment on
the last line. From a page load perspective, linked sourcemaps are only downloaded by Chrome
when Chrome DevTools is open. This means "production_sourcemap" strikes a good balance between
production performance and the ability to debug style issues in production.
Args:
name: Name of the target.
srcs: Sass source files (either .scss or .css files).
entry_point: A single Sass file that will be passed to the Sass compiler as the entry point.
out: Name of the output .css file.
mode: One of "development", "production_sourcemap" or "production".
deps: Any sass_library dependencies.
"""
if entry_point not in srcs:
fail("The entry_point must be included in the srcs.")
if mode not in ["development", "production_sourcemap", "production"]:
fail("Unknown value for \"mode\" argument: \"%s\"." % mode)
# Reference: https://sass-lang.com/documentation/cli/dart-sass.
sass_args = [
"--load-path=.",
]
if mode == "development":
sass_args += [
"--style=expanded",
# Ideally we would want to use --embed-sources and --embed-source-map simultaneously so
# that we only get the .css file as output, but for some reason the embedded sourcemap
# does not work in Chrome and makes csso crash. We work around this by omitting
# --embed-source-map and passing the resulting .css.map file to csso.
"--embed-sources",
]
elif mode == "production_sourcemap":
sass_args += [
"--style=compressed",
"--embed-sources",
]
else:
sass_args += [
"--style=compressed",
"--no-source-map",
]
out_unoptimized = name + "_unoptimized.css"
# See https://docs.aspect.build/rulesets/aspect_rules_js/docs/#using-binaries-published-to-npm.
_sass_bin.sass(
name = name + "_unoptimized",
srcs = srcs + deps,
args = sass_args + [
"$(rootpath %s)" % entry_point,
"$(rootpath %s)" % out_unoptimized,
],
outs = [out_unoptimized] if mode == "production" else [
out_unoptimized,
out_unoptimized + ".map",
],
)
# Reference: https://github.com/css/csso-cli.
csso_args = []
if mode == "development":
csso_args = [
"--input-source-map",
"$(rootpath %s.map)" % out_unoptimized,
"--source-map",
"inline",
]
elif mode == "production_sourcemap":
csso_args = [
"--input-source-map",
"$(rootpath %s.map)" % out_unoptimized,
"--source-map",
"file", # Causes csso to produce a <name>.css.map sourcemap file.
]
else:
csso_args = [
"--input-source-map",
"none",
"--source-map",
"none",
]
# See https://docs.aspect.build/rulesets/aspect_rules_js/docs/#using-binaries-published-to-npm.
_csso_bin.csso(
name = name,
srcs = [out_unoptimized] if mode == "production" else [
out_unoptimized,
out_unoptimized + ".map",
],
args = [
"$(rootpath %s)" % out_unoptimized,
"--output",
"$(rootpath %s)" % out,
] + csso_args,
outs = [out, out + ".map"] if mode == "production_sourcemap" else [out],
visibility = ["//visibility:public"],
)