Skip to content

bazel should support less  #31310

Closed
Closed

Description

@vthinkxie

🚀 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

Activity

  1. changed the title [-]bazel not support less loader[/-] [+]bazel should support less loader[/+] on Jun 27, 2019
  2. alan-agius4 commented on Jun 27, 2019

    @alan-agius4
    Contributor
  3. added
    area: bazelIssues related to the published `@angular/bazel` build rules
    featureLabel used to distinguish feature request from other issues
    on Jun 27, 2019
  4. added this to the Backlog milestone on Jun 27, 2019
  5. changed the title [-]bazel should support less loader[/-] [+]bazel should support less [/+] on Jun 27, 2019
  6. bcmedeiros commented on Jul 18, 2019

    @bcmedeiros

    Isn't it a cli issue?

  7. alexeagle commented on Aug 21, 2019

    @alexeagle
    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.

  8. marcus-sa commented on Nov 15, 2019

    @marcus-sa

    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.

  9. alexeagle commented on Nov 15, 2019

    @alexeagle
    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.

  10. alan-agius4 commented on Nov 15, 2019

    @alan-agius4
    Contributor
  11. alexw10 commented on Nov 21, 2019

    @alexw10

    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.

  12. alan-agius4 commented on Nov 21, 2019

    @alan-agius4
    Contributor

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

  13. henning96 commented on Nov 26, 2019

    @henning96

    @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?

  14. marcus-sa commented on Nov 26, 2019

    @marcus-sa

    @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
        )
  15. henning96 commented on Nov 26, 2019

    @henning96

    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.

  16. marcus-sa commented on Nov 26, 2019

    @marcus-sa

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

  17. angular-robot commented on Jun 4, 2021

    @angular-robot
    Contributor

    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.

  18. angular-robot commented on Jun 24, 2021

    @angular-robot
    Contributor

    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.

  19. alxhub commented on Mar 14, 2022

    @alxhub
    Member

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

  20. angular-automatic-lock-bot commented on Apr 14, 2022

    @angular-automatic-lock-bot

    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.

  21. locked and limited conversation to collaborators on Apr 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: bazelIssues related to the published `@angular/bazel` build rulesfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions