<filename>internal/js_library/js_library.bzl<gh_stars>100-1000
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"js_library can be used to expose and share any library package."

load(
    "//:providers.bzl",
    "DeclarationInfo",
    "ExternalNpmPackageInfo",
    "JSEcmaScriptModuleInfo",
    "JSModuleInfo",
    "JSNamedModuleInfo",
    "LinkablePackageInfo",
    "declaration_info",
    "js_ecma_script_module_info",
    "js_module_info",
    "js_named_module_info",
)
load(
    "//third_party/github.com/bazelbuild/bazel-skylib:rules/private/copy_file_private.bzl",
    "copy_bash",
    "copy_cmd",
)

_ATTRS = {
    "amd_names": attr.string_dict(
        doc = """Non-public legacy API, not recommended to make new usages.
        See documentation on AmdNamesInfo""",
    ),
    "deps": attr.label_list(),
    "is_windows": attr.bool(
        doc = "Internal use only. Automatically set by macro",
        mandatory = True,
    ),
    # module_name for legacy ts_library module_mapping support
    # which is still being used in a couple of tests
    # TODO: remove once legacy module_mapping is removed
    "module_name": attr.string(
        doc = "Internal use only. It will be removed soon.",
    ),
    "named_module_srcs": attr.label_list(
        doc = """Non-public legacy API, not recommended to make new usages.
        A subset of srcs that are javascript named-UMD or
        named-AMD for use in rules such as concatjs_devserver.
        They will be copied into the package bin folder if needed.""",
        allow_files = True,
    ),
    "package_name": attr.string(
        doc = """The package name that the linker will link this js_library as.

If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>.
If package_path is not set the this will be the root node_modules of the workspace.""",
    ),
    "package_path": attr.string(
        doc = """The package path in the workspace that the linker will link this js_library to.

If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>.
If package_path is not set the this will be the root node_modules of the workspace.""",
    ),
    "srcs": attr.label_list(allow_files = True),
    "strip_prefix": attr.string(
        doc = "Path components to strip from the start of the package import path",
        default = "",
    ),
}

AmdNamesInfo = provider(
    doc = "Non-public API. Provides access to the amd_names attribute of js_library",
    fields = {"names": """Mapping from require module names to global variables.
        This allows devmode JS sources to load unnamed UMD bundles from third-party libraries."""},
)

def write_amd_names_shim(actions, amd_names_shim, targets):
    """Shim AMD names for UMD bundles that were shipped anonymous.

    These are collected from our bootstrap deps (the only place global scripts should appear)

    Args:
      actions: starlark rule execution context.actions
      amd_names_shim: File where the shim is written
      targets: dependencies to be scanned for AmdNamesInfo providers
    """

    amd_names_shim_content = """// GENERATED by js_library.bzl
// Shim these global symbols which were defined by a bootstrap script
// so that they can be loaded with named require statements.
"""
    for t in targets:
        if AmdNamesInfo in t:
            for n in t[AmdNamesInfo].names.items():
                amd_names_shim_content += "define(\"%s\", function() { return %s });\n" % n
    actions.write(amd_names_shim, amd_names_shim_content)

JS_EXTENSIONS = ["js", "mjs", "cjs"]

def _is_javascript_file(file):
    for extension in JS_EXTENSIONS:
        if file.basename.endswith(".%s" % extension) or \
           file.basename.endswith(".%s.map" % extension):
            return True
    return False

def _to_manifest_path(ctx, file):
    if file.short_path.startswith("../"):
        return file.short_path[3:]
    else:
        return ctx.workspace_name + "/" + file.short_path

def _link_path(ctx, all_files):
    link_path = "/".join([p for p in [ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package] if p])

    # Strip a prefix from the package require path
    if ctx.attr.strip_prefix:
        link_path += "/" + ctx.attr.strip_prefix

        # Check that strip_prefix contains at least one src path
        check_prefix = "/".join([p for p in [
            ctx.label.workspace_name if ctx.label.workspace_name else ctx.workspace_name,
            ctx.label.package,
            ctx.attr.strip_prefix,
        ] if p])
        prefix_contains_src = False
        for file in all_files:
            if _to_manifest_path(ctx, file).startswith(check_prefix):
                prefix_contains_src = True
                break
        if not prefix_contains_src:
            fail("js_library %s strip_prefix path does not contain any of the provided sources" % ctx.label)

    return link_path

def _impl(ctx):
    input_files = ctx.files.srcs + ctx.files.named_module_srcs
    all_files = []
    typings = []
    js_files = []
    named_module_files = []

    for idx, f in enumerate(input_files):
        file = f

        # copy files into bin if needed
        if file.is_source and not file.path.startswith("external/"):
            dst = ctx.actions.declare_file(file.basename, sibling = file)
            if ctx.attr.is_windows:
                copy_cmd(ctx, file, dst)
            else:
                copy_bash(ctx, file, dst)

            # re-assign file to the one now copied into the bin folder
            file = dst

        # register js files
        if _is_javascript_file(file) or file.basename.endswith(".json"):
            js_files.append(file)

        # register typings
        if file.is_directory:
            # assume a directory contains typings since we can't know that it doesn't
            typings.append(file)
        elif (
            (
                file.path.endswith(".d.ts") or
                file.path.endswith(".d.ts.map") or
                # package.json may be required to resolve "typings" key
                file.path.endswith("/package.json")
            ) and
            # exclude eg. external/npm/node_modules/protobufjs/node_modules/@types/node/index.d.ts
            # these would be duplicates of the typings provided directly in another dependency.
            # also exclude all /node_modules/typescript/lib/lib.*.d.ts files as these are determined by
            # the tsconfig "lib" attribute
            len(file.path.split("/node_modules/")) < 3 and file.path.find("/node_modules/typescript/lib/lib.") == -1
        ):
            typings.append(file)

        # ctx.files.named_module_srcs are merged after ctx.files.srcs
        if idx >= len(ctx.files.srcs):
            named_module_files.append(file)

        # every single file on bin should be added here
        all_files.append(file)

    files_depset = depset(all_files)
    js_files_depset = depset(js_files)
    named_module_files_depset = depset(named_module_files)
    typings_depset = depset(typings)

    files_depsets = [files_depset]
    npm_sources_depsets = [files_depset]
    direct_ecma_script_module_depsets = [files_depset]
    direct_sources_depsets = [files_depset]
    direct_named_module_sources_depsets = [named_module_files_depset]
    typings_depsets = [typings_depset]
    js_files_depsets = [js_files_depset]

    for dep in ctx.attr.deps:
        if ExternalNpmPackageInfo in dep:
            npm_sources_depsets.append(dep[ExternalNpmPackageInfo].sources)
        else:
            if JSEcmaScriptModuleInfo in dep:
                direct_ecma_script_module_depsets.append(dep[JSEcmaScriptModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSEcmaScriptModuleInfo].direct_sources)
            if JSModuleInfo in dep:
                js_files_depsets.append(dep[JSModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSModuleInfo].direct_sources)
            if JSNamedModuleInfo in dep:
                direct_named_module_sources_depsets.append(dep[JSNamedModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSNamedModuleInfo].direct_sources)
            if DeclarationInfo in dep:
                typings_depsets.append(dep[DeclarationInfo].declarations)
                direct_sources_depsets.append(dep[DeclarationInfo].declarations)
            if DefaultInfo in dep:
                files_depsets.append(dep[DefaultInfo].files)

    providers = [
        DefaultInfo(
            files = depset(transitive = files_depsets),
            runfiles = ctx.runfiles(
                files = all_files,
                transitive_files = depset(
                    transitive = files_depsets + typings_depsets,
                ),
            ),
        ),
        AmdNamesInfo(names = ctx.attr.amd_names),
        js_ecma_script_module_info(
            sources = depset(transitive = direct_ecma_script_module_depsets),
            deps = ctx.attr.deps,
        ),
        js_module_info(
            sources = depset(transitive = js_files_depsets),
            deps = ctx.attr.deps,
        ),
        js_named_module_info(
            sources = depset(transitive = direct_named_module_sources_depsets),
            deps = ctx.attr.deps,
        ),
    ]

    if ctx.attr.package_name == "$node_modules$" or ctx.attr.package_name == "$node_modules_dir$":
        # special case for external npm deps
        workspace_name = ctx.label.workspace_name if ctx.label.workspace_name else ctx.workspace_name
        providers.append(ExternalNpmPackageInfo(
            direct_sources = depset(transitive = direct_sources_depsets),
            sources = depset(transitive = npm_sources_depsets),
            workspace = workspace_name,
            path = ctx.attr.package_path,
            has_directories = (ctx.attr.package_name == "$node_modules_dir$"),
        ))
    else:
        providers.append(LinkablePackageInfo(
            package_name = ctx.attr.package_name,
            package_path = ctx.attr.package_path,
            path = _link_path(ctx, all_files),
            files = depset(transitive = direct_sources_depsets),
        ))

    if len(typings) or len(typings_depsets) > 1:
        # Don't provide DeclarationInfo if there are no typings to provide.
        # Improves error messaging downstream if DeclarationInfo is required.
        decls = depset(transitive = typings_depsets)
        providers.append(declaration_info(
            declarations = decls,
            deps = ctx.attr.deps,
        ))
        providers.append(OutputGroupInfo(types = decls))
    elif ctx.attr.package_name == "$node_modules_dir$":
        # If this is directory artifacts npm package then always provide declaration_info
        # since we can't scan through files
        decls = depset(transitive = files_depsets)
        providers.append(declaration_info(
            declarations = decls,
            deps = ctx.attr.deps,
        ))
        providers.append(OutputGroupInfo(types = decls))

    return providers

_js_library = rule(
    implementation = _impl,
    attrs = _ATTRS,
)

def js_library(
        name,
        srcs = [],
        package_name = None,
        package_path = "",
        deps = [],
        **kwargs):
    """Groups JavaScript code so that it can be depended on like an npm package.

    `js_library` is intended to be used internally within Bazel, such as between two libraries in your monorepo.
    This rule doesn't perform any build steps ("actions") so it is similar to a `filegroup`.
    However it provides several Bazel "Providers" for interop with other rules.

    > Compare this to `pkg_npm` which just produces a directory output, and therefore can't expose individual
    > files to downstream targets and causes a cascading re-build of all transitive dependencies when any file
    > changes. Also `pkg_npm` is intended to publish your code for external usage outside of Bazel, like
    > by publishing to npm or artifactory, while `js_library` is for internal dependencies within your repo.

    `js_library` also copies any source files into the bazel-out folder.
    This is the same behavior as the `copy_to_bin` rule.
    By copying the complete package to the output tree, we ensure that the linker (our `npm link` equivalent)
    will make your source files available in the node_modules tree where resolvers expect them.
    It also means you can have relative imports between the files
    rather than being forced to use Bazel's "Runfiles" semantics where any program might need a helper library
    to resolve files between the logical union of the source tree and the output tree.

    ### Example

    A typical example usage of `js_library` is to expose some sources with a package name:

    ```python
    ts_project(
        name = "compile_ts",
        srcs = glob(["*.ts"]),
    )

    js_library(
        name = "my_pkg",
        # Code that depends on this target can import from "@myco/mypkg"
        package_name = "@myco/mypkg",
        # Consumers might need fields like "main" or "typings"
        srcs = ["package.json"],
        # The .js and .d.ts outputs from above will be part of the package
        deps = [":compile_ts"],
    )
    ```

    > To help work with "named AMD" modules as required by `concatjs_devserver` and other Google-style "concatjs" rules,
    > `js_library` has some undocumented advanced features you can find in the source code or in our examples.
    > These should not be considered a public API and aren't subject to our usual support and semver guarantees.

    ### Outputs

    Like all Bazel rules it produces a default output by providing [DefaultInfo].
    You'll get these outputs if you include this in the `srcs` of a typical rule like `filegroup`,
    and these will be the printed result when you `bazel build //some:js_library_target`.
    The default outputs are all of:
    - [DefaultInfo] produced by targets in `deps`
    - A copy of all sources (InputArtifacts from your source tree) in the bazel-out directory

    When there are TypeScript typings files, `js_library` provides [DeclarationInfo](#declarationinfo)
    so this target can be a dependency of a TypeScript rule. This includes any `.d.ts` files in `srcs` as well
    as transitive ones from `deps`.
    It will also provide [OutputGroupInfo] with a "types" field, so you can select the typings outputs with
    `bazel build //some:js_library_target --output_groups=types` or with a `filegroup` rule using the
    [output_group] attribute.

    In order to work with the linker (similar to `npm link` for first-party monorepo deps), `js_library` provides
    [LinkablePackageInfo](#linkablepackageinfo) for use with our "linker" that makes this package importable.

    It also provides:
    - [ExternalNpmPackageInfo](#externalnpmpackageinfo) to interop with rules that expect third-party npm packages.
    - [JSModuleInfo](#jsmoduleinfo) so rules like bundlers can collect the transitive set of .js files
    - [JSNamedModuleInfo](#jsnamedmoduleinfo) for rules that expect named AMD or `goog.module` format JS

    [OutputGroupInfo]: https://docs.bazel.build/versions/main/skylark/lib/OutputGroupInfo.html
    [DefaultInfo]: https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html
    [output_group]: https://docs.bazel.build/versions/main/be/general.html#filegroup.output_group

    Args:
        name: The name for the target
        srcs: The list of files that comprise the package
        package_name: The name it will be imported by. Should match the "name" field in the package.json file.

            If package_name == "$node_modules$" this indictates that this js_library target is one or more external npm
            packages in node_modules. This is a special case that used be covered by the internal only
            `external_npm_package` attribute. NB: '$' is an illegal character
            for npm packages names so this reserved name will not conflict with any valid package_name values

            This is used by the yarn_install & npm_install repository rules for npm dependencies installed by
            yarn & npm. When true, js_library will provide ExternalNpmPackageInfo.

            It can also be used for user-managed npm dependencies if node_modules is layed out outside of bazel.
            For example,

            ```starlark
            js_library(
                name = "node_modules",
                srcs = glob(
                    include = [
                        "node_modules/**/*.js",
                        "node_modules/**/*.d.ts",
                        "node_modules/**/*.json",
                        "node_modules/.bin/*",
                    ],
                    exclude = [
                        # Files under test & docs may contain file names that
                        # are not legal Bazel labels (e.g.,
                        # node_modules/ecstatic/test/public/中文/檔案.html)
                        "node_modules/**/test/**",
                        "node_modules/**/docs/**",
                        # Files with spaces in the name are not legal Bazel labels
                        "node_modules/**/* */**",
                        "node_modules/**/* *",
                    ],
                ),
                # Special value to provide ExternalNpmPackageInfo which is used by downstream
                # rules that use these npm dependencies
                package_name = "$node_modules$",
            )
            ```

            See `examples/user_managed_deps` for a working example of user-managed npm dependencies.
        package_path: The directory in the workspace to link to.
            If set, link this js_library to the node_modules under the package path specified.
            If unset, the default is to link to the node_modules root of the workspace.
        deps: Other targets that provide JavaScript code
        **kwargs: Other attributes
    """

    # Undocumented features
    amd_names = kwargs.pop("amd_names", {})
    module_name = kwargs.pop("module_name", None)
    named_module_srcs = kwargs.pop("named_module_srcs", [])

    if module_name:
        fail("use package_name instead of module_name in target //%s:%s" % (native.package_name(), name))
    if kwargs.pop("is_windows", None):
        fail("is_windows is set by the js_library macro and should not be set explicitly")

    _js_library(
        name = name,
        amd_names = amd_names,
        srcs = srcs,
        named_module_srcs = named_module_srcs,
        deps = deps,
        package_name = package_name,
        package_path = package_path,
        # module_name for legacy ts_library module_mapping support
        # which is still being used in a couple of tests
        # TODO: remove once legacy module_mapping is removed
        module_name = package_name if package_name != "$node_modules$" and package_name != "$node_modules_dir$" else None,
        is_windows = select({
            "@bazel_tools//src/conditions:host_windows": True,
            "//conditions:default": False,
        }),
        **kwargs
    )

<filename>internal/js_library/js_library.bzl<gh_stars>100-1000
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"js_library can be used to expose and share any library package."

load(
    "//:providers.bzl",
    "DeclarationInfo",
    "ExternalNpmPackageInfo",
    "JSEcmaScriptModuleInfo",
    "JSModuleInfo",
    "JSNamedModuleInfo",
    "LinkablePackageInfo",
    "declaration_info",
    "js_ecma_script_module_info",
    "js_module_info",
    "js_named_module_info",
)
load(
    "//third_party/github.com/bazelbuild/bazel-skylib:rules/private/copy_file_private.bzl",
    "copy_bash",
    "copy_cmd",
)

_ATTRS = {
    "amd_names": attr.string_dict(
        doc = """Non-public legacy API, not recommended to make new usages.
        See documentation on AmdNamesInfo""",
    ),
    "deps": attr.label_list(),
    "is_windows": attr.bool(
        doc = "Internal use only. Automatically set by macro",
        mandatory = True,
    ),
    # module_name for legacy ts_library module_mapping support
    # which is still being used in a couple of tests
    # TODO: remove once legacy module_mapping is removed
    "module_name": attr.string(
        doc = "Internal use only. It will be removed soon.",
    ),
    "named_module_srcs": attr.label_list(
        doc = """Non-public legacy API, not recommended to make new usages.
        A subset of srcs that are javascript named-UMD or
        named-AMD for use in rules such as concatjs_devserver.
        They will be copied into the package bin folder if needed.""",
        allow_files = True,
    ),
    "package_name": attr.string(
        doc = """The package name that the linker will link this js_library as.

If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>.
If package_path is not set the this will be the root node_modules of the workspace.""",
    ),
    "package_path": attr.string(
        doc = """The package path in the workspace that the linker will link this js_library to.

If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>.
If package_path is not set the this will be the root node_modules of the workspace.""",
    ),
    "srcs": attr.label_list(allow_files = True),
    "strip_prefix": attr.string(
        doc = "Path components to strip from the start of the package import path",
        default = "",
    ),
}

AmdNamesInfo = provider(
    doc = "Non-public API. Provides access to the amd_names attribute of js_library",
    fields = {"names": """Mapping from require module names to global variables.
        This allows devmode JS sources to load unnamed UMD bundles from third-party libraries."""},
)

def write_amd_names_shim(actions, amd_names_shim, targets):
    """Shim AMD names for UMD bundles that were shipped anonymous.

    These are collected from our bootstrap deps (the only place global scripts should appear)

    Args:
      actions: starlark rule execution context.actions
      amd_names_shim: File where the shim is written
      targets: dependencies to be scanned for AmdNamesInfo providers
    """

    amd_names_shim_content = """// GENERATED by js_library.bzl
// Shim these global symbols which were defined by a bootstrap script
// so that they can be loaded with named require statements.
"""
    for t in targets:
        if AmdNamesInfo in t:
            for n in t[AmdNamesInfo].names.items():
                amd_names_shim_content += "define(\"%s\", function() { return %s });\n" % n
    actions.write(amd_names_shim, amd_names_shim_content)

JS_EXTENSIONS = ["js", "mjs", "cjs"]

def _is_javascript_file(file):
    for extension in JS_EXTENSIONS:
        if file.basename.endswith(".%s" % extension) or \
           file.basename.endswith(".%s.map" % extension):
            return True
    return False

def _to_manifest_path(ctx, file):
    if file.short_path.startswith("../"):
        return file.short_path[3:]
    else:
        return ctx.workspace_name + "/" + file.short_path

def _link_path(ctx, all_files):
    link_path = "/".join([p for p in [ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package] if p])

    # Strip a prefix from the package require path
    if ctx.attr.strip_prefix:
        link_path += "/" + ctx.attr.strip_prefix

        # Check that strip_prefix contains at least one src path
        check_prefix = "/".join([p for p in [
            ctx.label.workspace_name if ctx.label.workspace_name else ctx.workspace_name,
            ctx.label.package,
            ctx.attr.strip_prefix,
        ] if p])
        prefix_contains_src = False
        for file in all_files:
            if _to_manifest_path(ctx, file).startswith(check_prefix):
                prefix_contains_src = True
                break
        if not prefix_contains_src:
            fail("js_library %s strip_prefix path does not contain any of the provided sources" % ctx.label)

    return link_path

def _impl(ctx):
    input_files = ctx.files.srcs + ctx.files.named_module_srcs
    all_files = []
    typings = []
    js_files = []
    named_module_files = []

    for idx, f in enumerate(input_files):
        file = f

        # copy files into bin if needed
        if file.is_source and not file.path.startswith("external/"):
            dst = ctx.actions.declare_file(file.basename, sibling = file)
            if ctx.attr.is_windows:
                copy_cmd(ctx, file, dst)
            else:
                copy_bash(ctx, file, dst)

            # re-assign file to the one now copied into the bin folder
            file = dst

        # register js files
        if _is_javascript_file(file) or file.basename.endswith(".json"):
            js_files.append(file)

        # register typings
        if file.is_directory:
            # assume a directory contains typings since we can't know that it doesn't
            typings.append(file)
        elif (
            (
                file.path.endswith(".d.ts") or
                file.path.endswith(".d.ts.map") or
                # package.json may be required to resolve "typings" key
                file.path.endswith("/package.json")
            ) and
            # exclude eg. external/npm/node_modules/protobufjs/node_modules/@types/node/index.d.ts
            # these would be duplicates of the typings provided directly in another dependency.
            # also exclude all /node_modules/typescript/lib/lib.*.d.ts files as these are determined by
            # the tsconfig "lib" attribute
            len(file.path.split("/node_modules/")) < 3 and file.path.find("/node_modules/typescript/lib/lib.") == -1
        ):
            typings.append(file)

        # ctx.files.named_module_srcs are merged after ctx.files.srcs
        if idx >= len(ctx.files.srcs):
            named_module_files.append(file)

        # every single file on bin should be added here
        all_files.append(file)

    files_depset = depset(all_files)
    js_files_depset = depset(js_files)
    named_module_files_depset = depset(named_module_files)
    typings_depset = depset(typings)

    files_depsets = [files_depset]
    npm_sources_depsets = [files_depset]
    direct_ecma_script_module_depsets = [files_depset]
    direct_sources_depsets = [files_depset]
    direct_named_module_sources_depsets = [named_module_files_depset]
    typings_depsets = [typings_depset]
    js_files_depsets = [js_files_depset]

    for dep in ctx.attr.deps:
        if ExternalNpmPackageInfo in dep:
            npm_sources_depsets.append(dep[ExternalNpmPackageInfo].sources)
        else:
            if JSEcmaScriptModuleInfo in dep:
                direct_ecma_script_module_depsets.append(dep[JSEcmaScriptModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSEcmaScriptModuleInfo].direct_sources)
            if JSModuleInfo in dep:
                js_files_depsets.append(dep[JSModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSModuleInfo].direct_sources)
            if JSNamedModuleInfo in dep:
                direct_named_module_sources_depsets.append(dep[JSNamedModuleInfo].direct_sources)
                direct_sources_depsets.append(dep[JSNamedModuleInfo].direct_sources)
            if DeclarationInfo in dep:
                typings_depsets.append(dep[DeclarationInfo].declarations)
                direct_sources_depsets.append(dep[DeclarationInfo].declarations)
            if DefaultInfo in dep:
                files_depsets.append(dep[DefaultInfo].files)

    providers = [
        DefaultInfo(
            files = depset(transitive = files_depsets),
            runfiles = ctx.runfiles(
                files = all_files,
                transitive_files = depset(
                    transitive = files_depsets + typings_depsets,
                ),
            ),
        ),
        AmdNamesInfo(names = ctx.attr.amd_names),
        js_ecma_script_module_info(
            sources = depset(transitive = direct_ecma_script_module_depsets),
            deps = ctx.attr.deps,
        ),
        js_module_info(
            sources = depset(transitive = js_files_depsets),
            deps = ctx.attr.deps,
        ),
        js_named_module_info(
            sources = depset(transitive = direct_named_module_sources_depsets),
            deps = ctx.attr.deps,
        ),
    ]

    if ctx.attr.package_name == "$node_modules$" or ctx.attr.package_name == "$node_modules_dir$":
        # special case for external npm deps
        workspace_name = ctx.label.workspace_name if ctx.label.workspace_name else ctx.workspace_name
        providers.append(ExternalNpmPackageInfo(
            direct_sources = depset(transitive = direct_sources_depsets),
            sources = depset(transitive = npm_sources_depsets),
            workspace = workspace_name,
            path = ctx.attr.package_path,
            has_directories = (ctx.attr.package_name == "$node_modules_dir$"),
        ))
    else:
        providers.append(LinkablePackageInfo(
            package_name = ctx.attr.package_name,
            package_path = ctx.attr.package_path,
            path = _link_path(ctx, all_files),
            files = depset(transitive = direct_sources_depsets),
        ))

    if len(typings) or len(typings_depsets) > 1:
        # Don't provide DeclarationInfo if there are no typings to provide.
        # Improves error messaging downstream if DeclarationInfo is required.
        decls = depset(transitive = typings_depsets)
        providers.append(declaration_info(
            declarations = decls,
            deps = ctx.attr.deps,
        ))
        providers.append(OutputGroupInfo(types = decls))
    elif ctx.attr.package_name == "$node_modules_dir$":
        # If this is directory artifacts npm package then always provide declaration_info
        # since we can't scan through files
        decls = depset(transitive = files_depsets)
        providers.append(declaration_info(
            declarations = decls,
            deps = ctx.attr.deps,
        ))
        providers.append(OutputGroupInfo(types = decls))

    return providers

_js_library = rule(
    implementation = _impl,
    attrs = _ATTRS,
)

def js_library(
        name,
        srcs = [],
        package_name = None,
        package_path = "",
        deps = [],
        **kwargs):
    """Groups JavaScript code so that it can be depended on like an npm package.

    `js_library` is intended to be used internally within Bazel, such as between two libraries in your monorepo.
    This rule doesn't perform any build steps ("actions") so it is similar to a `filegroup`.
    However it provides several Bazel "Providers" for interop with other rules.

    > Compare this to `pkg_npm` which just produces a directory output, and therefore can't expose individual
    > files to downstream targets and causes a cascading re-build of all transitive dependencies when any file
    > changes. Also `pkg_npm` is intended to publish your code for external usage outside of Bazel, like
    > by publishing to npm or artifactory, while `js_library` is for internal dependencies within your repo.

    `js_library` also copies any source files into the bazel-out folder.
    This is the same behavior as the `copy_to_bin` rule.
    By copying the complete package to the output tree, we ensure that the linker (our `npm link` equivalent)
    will make your source files available in the node_modules tree where resolvers expect them.
    It also means you can have relative imports between the files
    rather than being forced to use Bazel's "Runfiles" semantics where any program might need a helper library
    to resolve files between the logical union of the source tree and the output tree.

    ### Example

    A typical example usage of `js_library` is to expose some sources with a package name:

    ```python
    ts_project(
        name = "compile_ts",
        srcs = glob(["*.ts"]),
    )

    js_library(
        name = "my_pkg",
        # Code that depends on this target can import from "@myco/mypkg"
        package_name = "@myco/mypkg",
        # Consumers might need fields like "main" or "typings"
        srcs = ["package.json"],
        # The .js and .d.ts outputs from above will be part of the package
        deps = [":compile_ts"],
    )
    ```

    > To help work with "named AMD" modules as required by `concatjs_devserver` and other Google-style "concatjs" rules,
    > `js_library` has some undocumented advanced features you can find in the source code or in our examples.
    > These should not be considered a public API and aren't subject to our usual support and semver guarantees.

    ### Outputs

    Like all Bazel rules it produces a default output by providing [DefaultInfo].
    You'll get these outputs if you include this in the `srcs` of a typical rule like `filegroup`,
    and these will be the printed result when you `bazel build //some:js_library_target`.
    The default outputs are all of:
    - [DefaultInfo] produced by targets in `deps`
    - A copy of all sources (InputArtifacts from your source tree) in the bazel-out directory

    When there are TypeScript typings files, `js_library` provides [DeclarationInfo](#declarationinfo)
    so this target can be a dependency of a TypeScript rule. This includes any `.d.ts` files in `srcs` as well
    as transitive ones from `deps`.
    It will also provide [OutputGroupInfo] with a "types" field, so you can select the typings outputs with
    `bazel build //some:js_library_target --output_groups=types` or with a `filegroup` rule using the
    [output_group] attribute.

    In order to work with the linker (similar to `npm link` for first-party monorepo deps), `js_library` provides
    [LinkablePackageInfo](#linkablepackageinfo) for use with our "linker" that makes this package importable.

    It also provides:
    - [ExternalNpmPackageInfo](#externalnpmpackageinfo) to interop with rules that expect third-party npm packages.
    - [JSModuleInfo](#jsmoduleinfo) so rules like bundlers can collect the transitive set of .js files
    - [JSNamedModuleInfo](#jsnamedmoduleinfo) for rules that expect named AMD or `goog.module` format JS

    [OutputGroupInfo]: https://docs.bazel.build/versions/main/skylark/lib/OutputGroupInfo.html
    [DefaultInfo]: https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html
    [output_group]: https://docs.bazel.build/versions/main/be/general.html#filegroup.output_group

    Args:
        name: The name for the target
        srcs: The list of files that comprise the package
        package_name: The name it will be imported by. Should match the "name" field in the package.json file.

            If package_name == "$node_modules$" this indictates that this js_library target is one or more external npm
            packages in node_modules. This is a special case that used be covered by the internal only
            `external_npm_package` attribute. NB: '$' is an illegal character
            for npm packages names so this reserved name will not conflict with any valid package_name values

            This is used by the yarn_install & npm_install repository rules for npm dependencies installed by
            yarn & npm. When true, js_library will provide ExternalNpmPackageInfo.

            It can also be used for user-managed npm dependencies if node_modules is layed out outside of bazel.
            For example,

            ```starlark
            js_library(
                name = "node_modules",
                srcs = glob(
                    include = [
                        "node_modules/**/*.js",
                        "node_modules/**/*.d.ts",
                        "node_modules/**/*.json",
                        "node_modules/.bin/*",
                    ],
                    exclude = [
                        # Files under test & docs may contain file names that
                        # are not legal Bazel labels (e.g.,
                        # node_modules/ecstatic/test/public/中文/檔案.html)
                        "node_modules/**/test/**",
                        "node_modules/**/docs/**",
                        # Files with spaces in the name are not legal Bazel labels
                        "node_modules/**/* */**",
                        "node_modules/**/* *",
                    ],
                ),
                # Special value to provide ExternalNpmPackageInfo which is used by downstream
                # rules that use these npm dependencies
                package_name = "$node_modules$",
            )
            ```

            See `examples/user_managed_deps` for a working example of user-managed npm dependencies.
        package_path: The directory in the workspace to link to.
            If set, link this js_library to the node_modules under the package path specified.
            If unset, the default is to link to the node_modules root of the workspace.
        deps: Other targets that provide JavaScript code
        **kwargs: Other attributes
    """

    # Undocumented features
    amd_names = kwargs.pop("amd_names", {})
    module_name = kwargs.pop("module_name", None)
    named_module_srcs = kwargs.pop("named_module_srcs", [])

    if module_name:
        fail("use package_name instead of module_name in target //%s:%s" % (native.package_name(), name))
    if kwargs.pop("is_windows", None):
        fail("is_windows is set by the js_library macro and should not be set explicitly")

    _js_library(
        name = name,
        amd_names = amd_names,
        srcs = srcs,
        named_module_srcs = named_module_srcs,
        deps = deps,
        package_name = package_name,
        package_path = package_path,
        # module_name for legacy ts_library module_mapping support
        # which is still being used in a couple of tests
        # TODO: remove once legacy module_mapping is removed
        module_name = package_name if package_name != "$node_modules$" and package_name != "$node_modules_dir$" else None,
        is_windows = select({
            "@bazel_tools//src/conditions:host_windows": True,
            "//conditions:default": False,
        }),
        **kwargs
    )

# Copyright 2017 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #    http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Non-public legacy API, not recommended to make new usages.         See documentation on AmdNamesInfo # module_name for legacy ts_library module_mapping support # which is still being used in a couple of tests # TODO: remove once legacy module_mapping is removed Non-public legacy API, not recommended to make new usages.         A subset of srcs that are javascript named-UMD or         named-AMD for use in rules such as concatjs_devserver.         They will be copied into the package bin folder if needed. The package name that the linker will link this js_library as.  If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>. If package_path is not set the this will be the root node_modules of the workspace. The package path in the workspace that the linker will link this js_library to.  If package_path is set, the linker will link this package under <package_path>/node_modules/<package_name>. If package_path is not set the this will be the root node_modules of the workspace. Mapping from require module names to global variables.         This allows devmode JS sources to load unnamed UMD bundles from third-party libraries. Shim AMD names for UMD bundles that were shipped anonymous.      These are collected from our bootstrap deps (the only place global scripts should appear)      Args:       actions: starlark rule execution context.actions       amd_names_shim: File where the shim is written       targets: dependencies to be scanned for AmdNamesInfo providers // GENERATED by js_library.bzl // Shim these global symbols which were defined by a bootstrap script // so that they can be loaded with named require statements. # Strip a prefix from the package require path # Check that strip_prefix contains at least one src path # copy files into bin if needed # re-assign file to the one now copied into the bin folder # register js files # register typings # assume a directory contains typings since we can't know that it doesn't # package.json may be required to resolve "typings" key # exclude eg. external/npm/node_modules/protobufjs/node_modules/@types/node/index.d.ts # these would be duplicates of the typings provided directly in another dependency. # also exclude all /node_modules/typescript/lib/lib.*.d.ts files as these are determined by # the tsconfig "lib" attribute # ctx.files.named_module_srcs are merged after ctx.files.srcs # every single file on bin should be added here # special case for external npm deps # Don't provide DeclarationInfo if there are no typings to provide. # Improves error messaging downstream if DeclarationInfo is required. # If this is directory artifacts npm package then always provide declaration_info # since we can't scan through files Groups JavaScript code so that it can be depended on like an npm package.      `js_library` is intended to be used internally within Bazel, such as between two libraries in your monorepo.     This rule doesn't perform any build steps ("actions") so it is similar to a `filegroup`.     However it provides several Bazel "Providers" for interop with other rules.      > Compare this to `pkg_npm` which just produces a directory output, and therefore can't expose individual     > files to downstream targets and causes a cascading re-build of all transitive dependencies when any file     > changes. Also `pkg_npm` is intended to publish your code for external usage outside of Bazel, like     > by publishing to npm or artifactory, while `js_library` is for internal dependencies within your repo.      `js_library` also copies any source files into the bazel-out folder.     This is the same behavior as the `copy_to_bin` rule.     By copying the complete package to the output tree, we ensure that the linker (our `npm link` equivalent)     will make your source files available in the node_modules tree where resolvers expect them.     It also means you can have relative imports between the files     rather than being forced to use Bazel's "Runfiles" semantics where any program might need a helper library     to resolve files between the logical union of the source tree and the output tree.      ### Example      A typical example usage of `js_library` is to expose some sources with a package name:      ```python     ts_project(         name = "compile_ts",         srcs = glob(["*.ts"]),     )      js_library(         name = "my_pkg",         # Code that depends on this target can import from "@myco/mypkg"         package_name = "@myco/mypkg",         # Consumers might need fields like "main" or "typings"         srcs = ["package.json"],         # The .js and .d.ts outputs from above will be part of the package         deps = [":compile_ts"],     )     ```      > To help work with "named AMD" modules as required by `concatjs_devserver` and other Google-style "concatjs" rules,     > `js_library` has some undocumented advanced features you can find in the source code or in our examples.     > These should not be considered a public API and aren't subject to our usual support and semver guarantees.      ### Outputs      Like all Bazel rules it produces a default output by providing [DefaultInfo].     You'll get these outputs if you include this in the `srcs` of a typical rule like `filegroup`,     and these will be the printed result when you `bazel build //some:js_library_target`.     The default outputs are all of:     - [DefaultInfo] produced by targets in `deps`     - A copy of all sources (InputArtifacts from your source tree) in the bazel-out directory      When there are TypeScript typings files, `js_library` provides [DeclarationInfo](#declarationinfo)     so this target can be a dependency of a TypeScript rule. This includes any `.d.ts` files in `srcs` as well     as transitive ones from `deps`.     It will also provide [OutputGroupInfo] with a "types" field, so you can select the typings outputs with     `bazel build //some:js_library_target --output_groups=types` or with a `filegroup` rule using the     [output_group] attribute.      In order to work with the linker (similar to `npm link` for first-party monorepo deps), `js_library` provides     [LinkablePackageInfo](#linkablepackageinfo) for use with our "linker" that makes this package importable.      It also provides:     - [ExternalNpmPackageInfo](#externalnpmpackageinfo) to interop with rules that expect third-party npm packages.     - [JSModuleInfo](#jsmoduleinfo) so rules like bundlers can collect the transitive set of .js files     - [JSNamedModuleInfo](#jsnamedmoduleinfo) for rules that expect named AMD or `goog.module` format JS      [OutputGroupInfo]: https://docs.bazel.build/versions/main/skylark/lib/OutputGroupInfo.html     [DefaultInfo]: https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html     [output_group]: https://docs.bazel.build/versions/main/be/general.html#filegroup.output_group      Args:         name: The name for the target         srcs: The list of files that comprise the package         package_name: The name it will be imported by. Should match the "name" field in the package.json file.              If package_name == "$node_modules$" this indictates that this js_library target is one or more external npm             packages in node_modules. This is a special case that used be covered by the internal only             `external_npm_package` attribute. NB: '$' is an illegal character             for npm packages names so this reserved name will not conflict with any valid package_name values              This is used by the yarn_install & npm_install repository rules for npm dependencies installed by             yarn & npm. When true, js_library will provide ExternalNpmPackageInfo.              It can also be used for user-managed npm dependencies if node_modules is layed out outside of bazel.             For example,              ```starlark             js_library(                 name = "node_modules",                 srcs = glob(                     include = [                         "node_modules/**/*.js",                         "node_modules/**/*.d.ts",                         "node_modules/**/*.json",                         "node_modules/.bin/*",                     ],                     exclude = [                         # Files under test & docs may contain file names that                         # are not legal Bazel labels (e.g.,                         # node_modules/ecstatic/test/public/中文/檔案.html)                         "node_modules/**/test/**",                         "node_modules/**/docs/**",                         # Files with spaces in the name are not legal Bazel labels                         "node_modules/**/* */**",                         "node_modules/**/* *",                     ],                 ),                 # Special value to provide ExternalNpmPackageInfo which is used by downstream                 # rules that use these npm dependencies                 package_name = "$node_modules$",             )             ```              See `examples/user_managed_deps` for a working example of user-managed npm dependencies.         package_path: The directory in the workspace to link to.             If set, link this js_library to the node_modules under the package path specified.             If unset, the default is to link to the node_modules root of the workspace.         deps: Other targets that provide JavaScript code         **kwargs: Other attributes # Undocumented features # module_name for legacy ts_library module_mapping support # which is still being used in a couple of tests # TODO: remove once legacy module_mapping is removed