Skip to content

Commit

Permalink
feat: add support for linking js_library as 1p npm deps
Browse files Browse the repository at this point in the history
  • Loading branch information
gregmagolan committed Apr 14, 2024
1 parent 92c9b19 commit aaf9a93
Show file tree
Hide file tree
Showing 20 changed files with 286 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
# @generated
# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "//:pnpm-lock.yaml").
# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "@@//:pnpm-lock.yaml").
# This file should be checked into version control along with the pnpm-lock.yaml file.
.npmrc=-2065072158
pnpm-lock.yaml=-1732975623
pnpm-lock.yaml=-813031333
examples/npm_deps/patches/[email protected]=-442666336
package.json=-275319675
pnpm-workspace.yaml=-871530930
pnpm-workspace.yaml=-1178830835
examples/js_binary/package.json=-41174383
examples/macro/package.json=857146175
examples/npm_deps/package.json=283109008
examples/npm_deps/package.json=-1377141392
examples/npm_package/libs/lib_a/package.json=-1377103079
examples/npm_package/packages/pkg_a/package.json=-1053875011
examples/npm_package/packages/pkg_b/package.json=-994654274
examples/npm_package/packages/pkg_a/package.json=1006424040
examples/npm_package/packages/pkg_b/package.json=1041247977
examples/webpack_cli/package.json=1911342006
js/private/coverage/bundle/package.json=-1543718929
js/private/image/package.json=-1260474848
js/private/test/image/package.json=1286417612
js/private/test/js_run_devserver/package.json=-260856079
js/private/worker/src/package.json=1608383745
npm/private/test/package.json=1756993924
npm/private/test/vendored/lodash-4.17.21.tgz=-1206623349
npm/private/test/npm_package/package.json=-1991705133
npm/private/test/vendored/is-odd/package.json=1041695223
npm/private/test/vendored/semver-max/package.json=578664053
examples/linked_empty_node_modules/package.json=-1039372825
examples/npm_package/packages/pkg_d/package.json=1110895851
js/private/image/package.json=-1260474848
js/private/test/image/package.json=1286417612
js/private/test/js_run_devserver/package.json=-260856079
2 changes: 2 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ examples/npm_deps/node_modules/
examples/npm_package/libs/lib_a/node_modules/
examples/npm_package/packages/pkg_a/node_modules/
examples/npm_package/packages/pkg_b/node_modules/
examples/npm_package/packages/pkg_c/node_modules/
examples/npm_package/packages/pkg_d/node_modules/
examples/webpack_cli/node_modules/
js/private/coverage/bundle/node_modules
js/private/image/node_modules
Expand Down
17 changes: 17 additions & 0 deletions examples/npm_deps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,20 @@ js_test(
],
entry_point = "patched-dependencies-test.js",
)

#######################################
# Case 9: use a first-party npm package within our Bazel monorepo workspace from a js_library target

write_file(
name = "write9",
out = "case9.js",
content = ["require('@mycorp/pkg-d')"],
)

js_test(
name = "test9",
data = [
":node_modules/@mycorp/pkg-d",
],
entry_point = "case9.js",
)
2 changes: 2 additions & 0 deletions examples/npm_deps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"@aspect-test/c": "2.0.2",
"@gregmagolan/test-b": "0.0.2",
"@mycorp/pkg-a": "workspace:*",
"@mycorp/pkg-d": "workspace:*",
"@rollup/plugin-commonjs": "21.1.0",
"acorn": "8.7.1",
"debug": "3.2.7",
"meaning-of-life": "1.0.0",
"mobx-react": "7.3.0",
Expand Down
6 changes: 4 additions & 2 deletions examples/npm_package/packages/pkg_a/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "@mycorp/pkg-a",
"private": true,
"dependencies": {
"uuid": "8.3.2",
"acorn": "8.7.1"
"uuid": "8.3.2"
},
"peerDependencies": {
"acorn": "8.x.x"
}
}
23 changes: 7 additions & 16 deletions examples/npm_package/packages/pkg_b/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
load("@aspect_rules_js//npm:defs.bzl", "npm_package")
load("@aspect_rules_js//js:defs.bzl", "js_library")
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")

# Use js_library so that npm_package picks up our transitive sources from deps
# via the JsInfo provider.
js_library(
name = "lib",
# The terminal npm_package target for this package. This target is linked
# manually in the root of the pnpm workspace with `npm_link_package`.
npm_package(
name = "pkg_b",
srcs = [
"index.js",
"package.json",
],
data = [
# Runtime non-dev dependencies propagate to downstream binary and npm_link_package (via npm_package) targets
":node_modules/acorn",
":node_modules/uuid",
# npm deps must be added explicitly as this package is linked manually
# so deps are _not_ picked up from the pnpm lock file
":node_modules",
],
)

# The terminal npm_package target for this package. This target is linked
# manually in the root of the pnpm workspace with `npm_link_package`.
npm_package(
name = "pkg_b",
srcs = [":lib"],
include_runfiles = False,
package = "@mycorp/pkg-b",
visibility = ["//visibility:public"],
)
2 changes: 1 addition & 1 deletion examples/npm_package/packages/pkg_b/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# @mycorp/pkg-a package
# @mycorp/pkg-b package

'@mycorp/pkg-b' is an example of a package that is linked manually with `npm_link_package` where it is used.
6 changes: 4 additions & 2 deletions examples/npm_package/packages/pkg_b/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "@mycorp/pkg-b",
"private": true,
"dependencies": {
"uuid": "8.3.2",
"acorn": "8.7.1"
"uuid": "8.3.2"
},
"peerDependencies": {
"acorn": "8.x.x"
}
}
19 changes: 19 additions & 0 deletions examples/npm_package/packages/pkg_d/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@aspect_rules_js//js:defs.bzl", "js_library")
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")

js_library(
name = "pkg_d",
srcs = [
"index.js",
"package.json",
],
visibility = ["//visibility:public"],
# because we're linking this js_library, we must explictly add our npm dependendies to `deps` so
# they are picked up my the linker. npm dependendies in `data` are not propogated through the
# linker when linking a js_libary.
deps = [
":node_modules",
],
)
3 changes: 3 additions & 0 deletions examples/npm_package/packages/pkg_d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @mycorp/pkg-d package

'@mycorp/pkg-d' is an example of a first party package that is linked as a js_library
19 changes: 19 additions & 0 deletions examples/npm_package/packages/pkg_d/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @fileoverview minimal test program that requires a third-party package from npm
*/
const acorn = require('acorn')
const { v4: uuid } = require('uuid')

function toAst(program) {
return JSON.stringify(acorn.parse(program, { ecmaVersion: 2020 })) + '\n'
}

function getAcornVersion() {
return acorn.version
}

module.exports = {
toAst,
getAcornVersion,
uuid,
}
10 changes: 10 additions & 0 deletions examples/npm_package/packages/pkg_d/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@mycorp/pkg-d",
"private": true,
"dependencies": {
"uuid": "8.3.2"
},
"peerDependencies": {
"acorn": "8.x.x"
}
}
2 changes: 1 addition & 1 deletion js/private/js_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def _js_library_impl(ctx):
)

npm_package_store_infos = gather_npm_package_store_infos(
targets = ctx.attr.data + ctx.attr.deps,
targets = ctx.attr.srcs + ctx.attr.data + ctx.attr.deps,
)

runfiles = gather_runfiles(
Expand Down
79 changes: 61 additions & 18 deletions npm/private/npm_package_store.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ load(":utils.bzl", "utils")
load(":npm_package_info.bzl", "NpmPackageInfo")
load(":npm_package_store_info.bzl", "NpmPackageStoreInfo")

# buildifier: disable=bzl-visibility
load("//js/private:js_info.bzl", "JsInfo")

_DOC = """Defines a npm package that is linked into a node_modules tree.
The npm package is linked with a pnpm style symlinked node_modules output tree.
Expand All @@ -24,7 +27,6 @@ _ATTRS = {
"src": attr.label(
doc = """A npm_package target or or any other target that provides a NpmPackageInfo.
""",
providers = [NpmPackageInfo],
mandatory = True,
),
"deps": attr.label_keyed_string_dict(
Expand Down Expand Up @@ -150,28 +152,47 @@ If set, takes precendance over the package version in the NpmPackageInfo src.
}

def _npm_package_store_impl(ctx):
package = ctx.attr.package if ctx.attr.package else ctx.attr.src[NpmPackageInfo].package
version = ctx.attr.version if ctx.attr.version else ctx.attr.src[NpmPackageInfo].version
if ctx.attr.src:
if NpmPackageInfo in ctx.attr.src:
package = ctx.attr.package if ctx.attr.package else ctx.attr.src[NpmPackageInfo].package
version = ctx.attr.version if ctx.attr.version else ctx.attr.src[NpmPackageInfo].version
elif JsInfo in ctx.attr.src:
if not ctx.attr.package:
msg = "Expected package to be specified in '{}' when src '{}' provides a JsInfo".format(ctx.label, ctx.attr.src[JsInfo].target)
fail(msg)
package = ctx.attr.package
version = ctx.attr.version if ctx.attr.version else "0.0.0"
else:
msg = "Expected src of '{}' to provide either NpmPackageInfo or JsInfo".format(ctx.label)
fail(msg)
else:
# ctx.attr.src can be unspecified when the rule is a npm_package_store_internal; when it is _not_
# set, this is a terminal 3p package with ctx.attr.deps being the transitive closure of
# deps; this pattern is used to break circular dependencies between 3rd party npm deps; it
# is not used for 1st party deps
package = ctx.attr.package
version = ctx.attr.version

if not package:
fail("No package name specified to link to. Package name must either be specified explicitly via 'package' attribute or come from the 'src' 'NpmPackageInfo', typically a 'npm_package' target")
if not version:
fail("No package version specified to link to. Package version must either be specified explicitly via 'version' attribute or come from the 'src' 'NpmPackageInfo', typically a 'npm_package' target")

package_store_name = utils.package_store_name(package, version)

src = None
package_store_directory = None

files = []
transitive_files_depsets = []
transitive_package_store_infos_depsets = []
npm_package_store_infos = []
direct_ref_deps = {}

npm_package_store_infos = []
# the path to the package store location for this package
# "node_modules/{package_store_root}/{package_store_name}/node_modules/{package}"
package_store_directory_path = paths.join("node_modules", utils.package_store_root, package_store_name, "node_modules", package)

if ctx.attr.src:
if ctx.attr.src and NpmPackageInfo in ctx.attr.src:
# output the package as a TreeArtifact to its package store location
# "node_modules/{package_store_root}/{package_store_name}/node_modules/{package}"
package_store_directory_path = paths.join("node_modules", utils.package_store_root, package_store_name, "node_modules", package)

if ctx.label.workspace_name:
expected_short_path = paths.join("..", ctx.label.workspace_name, ctx.label.package, package_store_directory_path)
else:
Expand Down Expand Up @@ -253,7 +274,22 @@ deps of npm_package_store must be in the same package.""" % (ctx.label.package,
dep_symlink_path = paths.join("node_modules", utils.package_store_root, package_store_name, "node_modules", dep_package)
files.append(utils.make_symlink(ctx, dep_symlink_path, dep_package_store_directory.path))
npm_package_store_infos.append(store)
else:
elif ctx.attr.src and JsInfo in ctx.attr.src:
# Symlink to the directory of the target that created this JsInfo
if ctx.label.workspace_name:
symlink_path = paths.join("external", ctx.label.workspace_name, ctx.label.package, package_store_directory_path)
else:
symlink_path = paths.join(ctx.label.package, package_store_directory_path)
transitive_files_depsets.append(ctx.attr.src[JsInfo].transitive_sources)
transitive_files_depsets.append(ctx.attr.src[JsInfo].transitive_types)
transitive_package_store_infos_depsets.append(ctx.attr.src[JsInfo].npm_package_store_infos)
if ctx.attr.src[JsInfo].target.workspace_name:
target_path = paths.join(ctx.attr.src[JsInfo].bin_dir.path, "external", ctx.label.workspace_name, ctx.attr.src[JsInfo].target.package)
package_store_directory = utils.make_symlink(ctx, symlink_path, target_path)
else:
target_path = paths.join(ctx.attr.src[JsInfo].bin_dir.path, ctx.attr.src[JsInfo].target.package)
package_store_directory = utils.make_symlink(ctx, symlink_path, target_path)
elif not ctx.attr.src:
# ctx.attr.src can be unspecified when the rule is a npm_package_store_internal; when it is _not_
# set, this is a terminal 3p package with ctx.attr.deps being the transitive closure of
# deps; this pattern is used to break circular dependencies between 3rd party npm deps; it
Expand Down Expand Up @@ -292,6 +328,9 @@ deps of npm_package_store must be in the same package.""" % (ctx.label.package,
# "node_modules/{package_store_root}/{package_store_name}/node_modules/{package}"
dep_ref_dep_symlink_path = paths.join("node_modules", utils.package_store_root, dep_package_store_name, "node_modules", dep_ref_dep_alias)
files.append(utils.make_symlink(ctx, dep_ref_dep_symlink_path, dep_ref_def_package_store_directory.path))
else:
# We should _never_ get here
fail("Internal error")

if package_store_directory:
files.append(package_store_directory)
Expand All @@ -303,10 +342,14 @@ deps of npm_package_store must be in the same package.""" % (ctx.label.package,

files_depset = depset(files)

for transitive_package_store_infos_depset in transitive_package_store_infos_depsets:
for npm_package_store_info in transitive_package_store_infos_depset.to_list():
npm_package_store_infos.append(npm_package_store_info)

if ctx.attr.src:
transitive_files_depset = depset(files, transitive = [
npm_package_store.transitive_files
for npm_package_store in npm_package_store_infos
transitive_files_depset = depset(files, transitive = transitive_files_depsets + [
npm_package_store_info.transitive_files
for npm_package_store_info in npm_package_store_infos
])
else:
# ctx.attr.src can be unspecified when the rule is a npm_package_store_internal; when ctx.attr.src is
Expand All @@ -316,9 +359,9 @@ deps of npm_package_store must be in the same package.""" % (ctx.label.package,
# closure of all the entire package store deps, we can safely add just `files` from each of
# these to `transitive_files_depset`; doing so reduces the size of `transitive_files_depset`
# significantly and reduces analysis time and Bazel memory usage during analysis
transitive_files_depset = depset(files, transitive = [
npm_package_store.files
for npm_package_store in npm_package_store_infos
transitive_files_depset = depset(files, transitive = transitive_files_depsets + [
npm_package_store_info.files
for npm_package_store_info in npm_package_store_infos
])

providers = [
Expand All @@ -336,7 +379,7 @@ deps of npm_package_store must be in the same package.""" % (ctx.label.package,
dev = ctx.attr.dev,
),
]
if package_store_directory:
if package_store_directory and package_store_directory.is_directory:
# Provide an output group that provides a single file which is the
# package directory for use in $(execpath) and $(rootpath).
# Output group name must match utils.package_directory_output_group
Expand Down
2 changes: 0 additions & 2 deletions npm/private/npm_package_store_internal.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

load("@bazel_skylib//lib:dicts.bzl", "dicts")
load(":npm_package_store.bzl", _npm_package_store_lib = "npm_package_store_lib")
load(":npm_package_info.bzl", "NpmPackageInfo")

_INTERNAL_ATTRS_STORE = dicts.add(_npm_package_store_lib.attrs, {
"src": attr.label(
Expand All @@ -15,7 +14,6 @@ _INTERNAL_ATTRS_STORE = dicts.add(_npm_package_store_lib.attrs, {
complication. Outside our `npm_import` you should structure you `npm_link_package` targets in
a DAG (without cycles).
""",
providers = [NpmPackageInfo],
),
"package": attr.string(
doc = """The package name to link to.
Expand Down
Loading

0 comments on commit aaf9a93

Please sign in to comment.