Skip to content
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

bazel should support less #31310

Closed
vthinkxie opened this issue Jun 27, 2019 · 16 comments
Closed

bazel should support less #31310

vthinkxie opened this issue Jun 27, 2019 · 16 comments
Labels
area: bazel Issues related to the published `@angular/bazel` build rules feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Milestone

Comments

@vthinkxie
Copy link
Contributor

🚀 Feature request

Command (mark with an x)

- [x] new
- [x] build
- [x] serve
- [ ] test
- [ ] e2e
- [ ] generate
- [ ] add
- [ ] update
- [ ] lint
- [ ] xi18n
- [ ] run
- [ ] config
- [ ] help
- [ ] version
- [ ] doc

Description

Angular or Angular CLI should support less loader

Describe the solution you'd like

Angular CLI support ng new with bazel and less loader before @angular/cli@8.0.3
but there is a bug #31209

The less option was removed after 8.1.0-rc.0 with #31234

Angular CLI should support less when run

ng new --collection=@angular/bazel

Describe alternatives you've considered

Angular CLI should support less option with bazel

@vthinkxie vthinkxie changed the title bazel not support less loader bazel should support less loader Jun 27, 2019
@alan-agius4
Copy link
Contributor

Related to bazel-contrib/rules_nodejs#887

@alan-agius4 alan-agius4 transferred this issue from angular/angular-cli Jun 27, 2019
@alan-agius4 alan-agius4 reopened this Jun 27, 2019
@alan-agius4 alan-agius4 added area: bazel Issues related to the published `@angular/bazel` build rules feature Issue that requests a new feature labels Jun 27, 2019
@ngbot ngbot bot added this to the Backlog milestone Jun 27, 2019
@alan-agius4 alan-agius4 changed the title bazel should support less loader bazel should support less Jun 27, 2019
@bcmedeiros
Copy link

Isn't it a cli issue?

@alexeagle
Copy link
Contributor

There is now a Bazel rule for running less: https://bazelbuild.github.io/rules_nodejs/Less.html

You can already add this manually if you --leaveBazelFilesOnDisk and configure a less_binary.

I'll let @kyliau decide about keeping this issue open for the schematic that generates the default Bazel configuration.

@marcus-sa
Copy link

I've created some rules_less internally at work based off rules_sass, which works with plugins as well.
I can make the rules open-source if you'd like.

@alexeagle
Copy link
Contributor

@marcus-sa what do your rules_less do that couldn't be done in https://github.com/bazelbuild/rules_nodejs/tree/master/examples/app/styles - can we extend that example to cover use cases like less plugins?
I think we should not have less rules.

The question in this issue is really more about auto-configuration. Right now the @angular/bazel package is hard-coded to write Bazel configs that understand Sass but not Less. We could fix that by making more complex Bazel config, but our current strategy with @alan-agius4 is to just run the code in @angular-devkit/build-angular so that all existing Angular apps run under Bazel.

@alan-agius4
Copy link
Contributor

alan-agius4 commented Nov 15, 2019 via email

@alexw10
Copy link

alexw10 commented Nov 21, 2019

So right now if my Angular application is utilizing LESS does this mean bazel will not work with it very well?
Should we convert to Sass?
I just tried utilize ng add @angular/bazel and of course did not work cause of LESS issues because or app is pretty much exclusively utilizing that.

@alan-agius4
Copy link
Contributor

@alexw10, LESS will be supported with the strategy with @alexeagle explained above.

@henning96
Copy link

@alexeagle Thanks for the example, but (as far as I understand this), your example will only work for single files. This is not suitable for big Angular modules with less files for each component. We need at least the functionality to select the less files by a glob pattern and compile all of those. Can you please provide such an example?

@marcus-sa
Copy link

marcus-sa commented Nov 26, 2019

@henning96
Here's a SASS equivalent for LESS, with the support for all LESS features such as plugins and CLI flags.

load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")

LessInfo = provider(
    doc = "Collects files from less_library for use in downstream less_binary",
    fields = {
        "transitive_sources": "Less sources for this target and its dependencies",
    },
)

_ALLOWED_SRC_FILE_EXTENSIONS = [".less", ".woff", ".ttf", ".css", ".svg", ".png", ".gif", ".cur", ".jpg", ".webp"]

def _collect_transitive_sources(srcs, deps):
    "Less compilation requires all transitive .less source files"
    return depset(
        srcs,
        transitive = [dep[LessInfo].transitive_sources for dep in deps],
        # Provide .less sources from dependencies first
        order = "postorder",
    )

def _strip_extension(path):
    """Removes the final extension from a path."""
    components = path.split(".")
    components.pop()
    return ".".join(components)

less_deps_attr = attr.label_list(
    doc = "less_library targets to include in the compilation",
    providers = [LessInfo],
    allow_files = False,
)

def _less_library_impl(ctx):
    """less_library collects all transitive sources for given srcs and deps.

    It doesn't execute any actions.

    Args:
      ctx: The Bazel build context

    Returns:
      The less_library rule.
    """

    transitive_sources = _collect_transitive_sources(
        ctx.files.srcs,
        ctx.attr.deps,
    )

    return [
        LessInfo(transitive_sources = transitive_sources),
        DefaultInfo(
            files = transitive_sources,
            runfiles = ctx.runfiles(transitive_files = transitive_sources),
        ),
    ]

less_library = rule(
    implementation = _less_library_impl,
    attrs = {
        "srcs": attr.label_list(
            doc = "Less source files",
            allow_files = _ALLOWED_SRC_FILE_EXTENSIONS,
            allow_empty = False,
            mandatory = True,
        ),
        "deps": less_deps_attr,
    },
)

def _run_less(ctx, input, css_output, map_output = None):
    args = ctx.actions.args()

    inputs = _collect_transitive_sources([ctx.file.src], ctx.attr.deps)

    args.add_all([input.path, css_output.path])

    include_path = "--include-path='"

    for prefix in [".", ctx.var["BINDIR"], ctx.var["GENDIR"]]:
        include_path += "%s/;" % prefix
        for path in ctx.attr.include_paths:
            include_path += "%s/%s;" % (prefix, path)

    args.add("%s'" % include_path)

    if ctx.attr.sourcemap:
        args.add("--source-map=%s" % map_output.path)

    if ctx.attr.inline_js:
        args.add("--js")

    if ctx.attr.strict_units:
        args.add("--strict_units=on")

    if ctx.attr.url_args:
        args.add("--url-args=%s" % ctx.attr.url_args)

    if ctx.attr.rewrite_urls:
        args.add("--rewrite-urls=%s" % ctx.attr.rewrite_urls)

    if ctx.attr.plugins:
        for name, options in ctx.attr.plugins.items():
            plugin = "--%s" % name

            if options:
                plugin += "=%s" % options

            args.add(plugin)

    ctx.actions.run(
        mnemonic = "LessCompiler",
        executable = ctx.executable.compiler,
        inputs = inputs,
        use_default_shell_env = True,
        tools = [ctx.executable.compiler],
        arguments = [args],
        outputs = [css_output, map_output] if map_output else [css_output],
    )

def _less_binary_impl(ctx):
    # Make sure the output CSS is available in runfiles if used as a data dep.
    if ctx.attr.sourcemap:
        map_file = ctx.outputs.map_file
        outputs = [ctx.outputs.css_file, map_file]
    else:
        map_file = None
        outputs = [ctx.outputs.css_file]

    _run_less(ctx, ctx.file.src, ctx.outputs.css_file, map_file)
    return DefaultInfo(runfiles = ctx.runfiles(files = outputs))

_less_binary_attrs = {
    "src": attr.label(
        doc = "Less entrypoint file",
        mandatory = True,
        allow_single_file = _ALLOWED_SRC_FILE_EXTENSIONS,
    ),
    "sourcemap": attr.bool(
        default = True,
        doc = "Whether source maps should be omitted",
    ),
    "output_dir": attr.string(
        doc = "Output directory, relative to this package.",
        default = "",
    ),
    "include_paths": attr.string_list(
        doc = "Additional directories to search when resolving imports",
    ),
    "strict_units": attr.bool(
        doc = "Without this option, Less attempts to guess at the output unit when it does maths",
        default = False,
    ),
    "plugins": attr.string_dict(),
    "inline_js": attr.bool(
        doc = "Enables evaluation of JavaScript inline in .less files",
        # default = False,
    ),
    "url_args": attr.string(
        doc = """
        This option allows you to specify a argument to go on to every URL.
        This may be used for cache-busting for instance.
        """,
    ),
    # TODO: Figure out CLI args
    #    "modify_vars": attr.string_list_dict(
    #        doc = """
    #        As opposed to the global variable option,
    #        this puts the declaration at the end of your base file,
    #        meaning it will override anything defined in your Less file.
    #        """,
    #    ),
    #    "global_vars": attr.string_list_dict(
    #        doc = """
    #        This option defines a variable that can be referenced by the file.
    #        Effectively the declaration is put at the top of your base Less file,
    #        meaning it can be used but it also can be overridden if this variable is defined in the file.
    #        """
    #    ),
    "rewrite_urls": attr.string(
        default = "off",
        values = [
            "off",
            "all",
            "local",
        ],
    ),
    "output_name": attr.string(
        doc = """
        Name of the output file, including the .css extension.
        By default, this is based on the `src` attribute: if `styles.scss` is
        the `src` then the output file is `styles.css.`.
        You can override this to be any other name.
        Note that some tooling may assume that the output name is derived from
        the input name, so use this attribute with caution.
        """,
        default = "",
    ),
    "deps": less_deps_attr,
    "compiler": attr.label(
        default = Label("//tools/less:less_compiler"),
        executable = True,
        cfg = "host",
    ),
}

def _less_binary_outputs(src, output_name, output_dir, sourcemap):
    """Get map of less_binary outputs, including generated css and sourcemaps.

    Note that the arguments to this function are named after attributes on the rule.

    Args:
      src: The rule's `src` attribute
      output_name: The rule's `output_name` attribute
      output_dir: The rule's `output_dir` attribute
      sourcemap: The rule's `sourcemap` attribute

    Returns:
      Outputs for the less_binary
    """

    output_name = output_name or _strip_extension(src.name) + ".css"
    css_file = "/".join([p for p in [output_dir, output_name] if p])

    outputs = {
        "css_file": css_file,
    }

    if sourcemap:
        outputs["map_file"] = "%s.map" % css_file

    return outputs

less_binary = rule(
    implementation = _less_binary_impl,
    attrs = _less_binary_attrs,
    outputs = _less_binary_outputs,
)

def less_binary_macro(name, plugins = {}, **kwargs):
    compiler_name = "%s_bin" % name

    # this is the less plugin naming convention
    data = ["@npm//less-plugin-%s" % p for p in plugins.keys()]

    nodejs_binary(
        name = compiler_name,
        entry_point = "@npm//:node_modules/less/bin/lessc",
        install_source_map_support = False,
        data = data + [
            "@npm//less",
        ],
        visibility = ["//visibility:private"],
    )

    less_binary(
        name = name,
        plugins = plugins,
        compiler = ":" + compiler_name,
        **kwargs
    )

@henning96
Copy link

Exactly, something like this. Thank you @marcus-sa. Can we please add this to https://github.com/bazelbuild/rules_nodejs? Maybe a minimal version of this that only supports the most common arguments.

@marcus-sa
Copy link

marcus-sa commented Nov 26, 2019

@henning96 I'll create a separate repository for it instead 😃

@angular-robot angular-robot bot added the feature: votes required Feature request which is currently still in the voting phase label Jun 4, 2021
@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 4, 2021

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 24, 2021

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.

@angular-robot angular-robot bot added the feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors label Jun 24, 2021
@alxhub
Copy link
Member

alxhub commented Mar 14, 2022

Closing as obsolete - the Bazel builder and associated CLI functionality are deprecated and no longer supported.

@alxhub alxhub closed this as completed Mar 14, 2022
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: bazel Issues related to the published `@angular/bazel` build rules feature: insufficient votes Label to add when the not a sufficient number of votes or comments from unique authors feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests

8 participants