Skip to content

Commit

Permalink
Add a @npm//node_modules/package:package__umd named_umd_bundle targ…
Browse files Browse the repository at this point in the history
…et to npm packages.

This generates a named-UMD bundle @npm//node_modules/package:package.umd.js if this file does not already exists. For npm packages that don’t ship with named UMD bundles, this target can be used to supply one to rules such as ts_devserver & ts_web_test_suite.
  • Loading branch information
gregmagolan committed May 9, 2019
1 parent 39c941d commit 72f2df5
Show file tree
Hide file tree
Showing 35 changed files with 54,890 additions and 48 deletions.
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ npm_package(
replacements = COMMON_REPLACEMENTS,
deps = [
"//third_party/github.com/bazelbuild/bazel-skylib:package_contents",
"//third_party/github.com/browserify/browserify:package_contents",
"//third_party/github.com/buffer-from:package_contents",
"//third_party/github.com/gjtorikian/isBinaryFile:package_contents",
"//third_party/github.com/jhermsmeier/browserify-named-amd:package_contents",
"//third_party/github.com/source-map:package_contents",
"//third_party/github.com/source-map-support:package_contents",
"//internal:package_contents",
Expand Down
9 changes: 5 additions & 4 deletions e2e/ts_devserver/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ exports_files([
ts_library(
name = "app",
srcs = ["app.ts"],
deps = ["@npm//date-fns"],
)

ts_devserver(
name = "devserver",
entry_module = "e2e_ts_devserver/app",
port = 8080,
# This is the path we'll request from the browser, see index.html
serving_path = "/bundle.min.js",
# The devserver can serve our static files too
static_files = ["index.html"],
scripts = [
"@npm//node_modules/date-fns:date-fns.umd.js",
],
# We'll collect all the devmode JS sources from these TypeScript libraries
deps = [":app"],
)
Expand Down
5 changes: 4 additions & 1 deletion e2e/ts_devserver/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {format} from 'date-fns';

const el: HTMLDivElement = document.createElement('div');
el.innerText = 'Hello, TypeScript';
const date: string = format(new Date(2019, 4, 7), 'MMMM D, YYYY');
el.innerText = `Hello, TypeScript today is ${date}`;
el.className = 'ts1';
document.body.appendChild(el);
4 changes: 2 additions & 2 deletions e2e/ts_devserver/app_e2e_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ describe('app', () => {
browser.wait(ExpectedConditions.presenceOf(element(by.css('div.ts1'))), timeoutMs);
}, timeoutMs);

it('should display: Hello, TypeScript', (done) => {
it('should display: Hello, TypeScript today is May 7, 2019', (done) => {
const div = element(by.css('div.ts1'));
div.getText().then(t => expect(t).toEqual(`Hello, TypeScript`));
div.getText().then(t => expect(t).toEqual(`Hello, TypeScript today is May 7, 2019`));
done();
});
});
21 changes: 0 additions & 21 deletions e2e/ts_devserver/index.html

This file was deleted.

1 change: 1 addition & 0 deletions e2e/ts_devserver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@types/jasmine": "2.8.2",
"@types/node": "7.0.18",
"concurrently": "^3.5.1",
"date-fns": "1.30.1",
"jasmine": "2.8.0",
"protractor": "^5.2.0",
"typescript": "2.7.x"
Expand Down
6 changes: 3 additions & 3 deletions e2e/ts_devserver/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# yarn lockfile v1


"@bazel/typescript@file:../../dist/npm_bazel_typescript$6315":
version "0.27.10-4-gca50827"
"@bazel/typescript@file:../../dist/npm_bazel_typescript$7669":
version "0.28.0-4-g8b6ad28"
dependencies:
protobufjs "6.8.8"
semver "5.6.0"
Expand Down Expand Up @@ -306,7 +306,7 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"

date-fns@^1.23.0:
date-fns@1.30.1, date-fns@^1.23.0:
version "1.30.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
Expand Down
12 changes: 12 additions & 0 deletions internal/npm_install/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//:defs.bzl", "nodejs_binary")

bzl_library(
name = "bzl",
Expand All @@ -25,3 +26,14 @@ filegroup(
),
visibility = ["//:__pkg__"],
)

nodejs_binary(
name = "browserify-wrapped",
data = [
":browserify-wrapped.js",
"//third_party/github.com/browserify/browserify:sources",
],
entry_point = "build_bazel_rules_nodejs/internal/npm_install/browserify-wrapped.js",
install_source_map_support = False,
visibility = ["//visibility:public"],
)
62 changes: 62 additions & 0 deletions internal/npm_install/browserify-wrapped.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @license
* 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.
*/

// A wrapper around browserify necessary since an absolute path is needed
// for browserify to find the named-amd plugin

const child_process = require('child_process');

const DEBUG = false;

runBrowserify(...process.argv.slice(2));

function runBrowserify(workspaceName, packageName, entryPoint, output) {
if (DEBUG)
console.error(`
browserify-wrapped: running with
cwd: ${process.cwd()}
workspaceName: ${workspaceName},
packageName: ${packageName}
entryPoint: ${entryPoint}
output: ${output}`);

// browserify is ncc bundled & terser minified into third_party
const browserify = require.resolve(
'build_bazel_rules_nodejs/third_party/github.com/browserify/browserify/index.min.js');

// named-amd plugin is vendored in under third_party
const namedAmd = require.resolve(
'build_bazel_rules_nodejs/third_party/github.com/jhermsmeier/browserify-named-amd/named-amd.js');

const args = [
browserify, entryPoint,
// Supply the name to use for the AMD define with named-amd plugin
'-p', '[', namedAmd, '--name', packageName, ']',
// Output a stand-alone UMD bundle. Sanitized version the supplied name is used for
// the global name (see https://github.com/ForbesLindesay/umd#name-casing-and-characters).
// Global name set to `browserify_${workspaceName}_${packageName}` so there are no
// conflicts with other globals.
'-s', `browserify_${workspaceName}_${packageName}`, '-o', output
];

if (DEBUG) console.error(`\nRunning: node ${args.join(' ')}\n`);

const isWindows = /^win/i.test(process.platform);
child_process.execFileSync(
isWindows ? 'node.cmd' : 'node', args,
{stdio: [process.stdin, process.stdout, process.stderr]});
}
84 changes: 75 additions & 9 deletions internal/npm_install/generate_build_file.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
const fs = require('fs');
const path = require('path');

const DEBUG = false;

const BUILD_FILE_HEADER = `# Generated file from yarn_install/npm_install rule.
# See $(bazel info output_base)/external/build_bazel_rules_nodejs/internal/npm_install/generate_build_file.js
Expand Down Expand Up @@ -587,17 +589,35 @@ function isValidBinPathObjectValues(entry) {
}

/**
* Given a path, remove './' if it exists.
* Cleanup a package.json "bin" path.
*
* Bin paths usually come in 2 flavors: './bin/foo' or 'bin/foo',
* sometimes other stuff like 'lib/foo'. Remove prefix './' if it
* exists.
*/
function cleanupBinPath(p) {
p = p.replace(/\\/g, '/');
if (p.indexOf('./') === 0) {
p = p.slice(2);
}
return p;
}

/**
* Cleanup a package.json entry point such as "main"
*
* Removes './' if it exists.
* Appends `index.js` if p ends with `/`.
*/
function cleanupBinPath(path) {
// Bin paths usually come in 2 flavors: './bin/foo' or 'bin/foo',
// sometimes other stuff like 'lib/foo'. Remove prefix './' if it
// exists.
path = path.replace(/\\/g, '/');
if (path.indexOf('./') === 0) {
path = path.slice(2);
function cleanupEntryPointPath(p) {
p = p.replace(/\\/g, '/');
if (p.indexOf('./') === 0) {
p = p.slice(2);
}
if (p.endsWith('/')) {
p += 'index.js';
}
return path;
return p;
}

/**
Expand Down Expand Up @@ -731,6 +751,19 @@ function getNgApfScripts(pkg) {
[];
}

/**
* Looks for a file within a package and returns it if found.
*/
function findFile(pkg, m) {
const ml = m.toLowerCase();
for (const f of pkg._files) {
if (f.toLowerCase() === ml) {
return f;
}
}
return undefined;
}

/**
* Given a pkg, return the skylark `node_module_library` targets for the package.
*/
Expand Down Expand Up @@ -803,6 +836,39 @@ node_module_library(
`;

let mainEntryPoint = pkg.main ? cleanupEntryPointPath(pkg.main) : undefined;
if (mainEntryPoint) {
// check if main entry point exists
mainEntryPoint = findFile(pkg, mainEntryPoint) || findFile(pkg, `${mainEntryPoint}.js`);
if (!mainEntryPoint) {
// If "main" entry point listed could not be resolved to a file
// then don't create an npm_umd_bundle target. This can happen
// in some npm packages that list an incorrect main such as [email protected]
// which lists `"main": "index.js"` but that file does not exist.
if (DEBUG)
console.error(`Could not find "main" entry point ${pkg.main} in npm package ${pkg._name}`);
}
} else {
// if "main" is not specified then look for a root index.js
mainEntryPoint = findFile(pkg, 'index.js');
}

// add an `npm_umd_bundle` target to generate an UMD bundle if one does
// not exists
if (mainEntryPoint && !findFile(pkg, `${pkg._name}.umd.js`)) {
result +=
`load("@build_bazel_rules_nodejs//internal/npm_install:npm_umd_bundle.bzl", "npm_umd_bundle")
npm_umd_bundle(
name = "${pkg._name}__umd",
package_name = "${pkg._name}",
entry_point = ":${mainEntryPoint}",
package = ":${pkg._name}__pkg",
)
`;
}

if (pkg._executables) {
for (const [name, path] of pkg._executables.entries()) {
// Handle additionalAttributes of format:
Expand Down
89 changes: 89 additions & 0 deletions internal/npm_install/npm_umd_bundle.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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.

"""Node package UMD bundling
For use by yarn_install and npm_install. Not meant to be part of the public API.
"""

load("@build_bazel_rules_nodejs//internal/common:node_module_info.bzl", "NodeModuleSources", "collect_node_modules_aspect")

def _npm_umd_bundle(ctx):
if len(ctx.attr.entry_point.files) != 1:
fail("labels in entry_point must contain exactly one file")

output = ctx.actions.declare_file("%s.umd.js" % ctx.attr.package_name)

args = ctx.actions.args()

args.add(ctx.workspace_name)
args.add(ctx.attr.package_name)
args.add(ctx.file.entry_point.path)
args.add(output.path)

sources = ctx.attr.package[NodeModuleSources].sources.to_list()

# Only pass .js files as inputs to browserify
inputs = [f for f in sources if f.path.endswith(".js")]

ctx.actions.run(
progress_message = "Generated UMD bundle for %s npm package [browserify]" % ctx.attr.package_name,
executable = ctx.executable._browserify_wrapped,
inputs = inputs,
outputs = [output],
arguments = [args],
)

return [
DefaultInfo(files = depset([output]), runfiles = ctx.runfiles([output])),
OutputGroupInfo(umd = depset([output])),
]

NPM_UMD_ATTRS = {
"package_name": attr.string(
doc = """The name of the npm package""",
mandatory = True,
),
"entry_point": attr.label(
doc = """Entry point for the npm package""",
mandatory = True,
allow_single_file = True,
),
"package": attr.label(
doc = """The npm package target""",
mandatory = True,
aspects = [collect_node_modules_aspect],
),
"_browserify_wrapped": attr.label(
executable = True,
cfg = "host",
default = Label("@build_bazel_rules_nodejs//internal/npm_install:browserify-wrapped"),
),
}

npm_umd_bundle = rule(
implementation = _npm_umd_bundle,
attrs = NPM_UMD_ATTRS,
outputs = {"umd": "%{package_name}.umd.js"},
)
"""Node package umd bundling
"""
# TODO(gregolan): add the above docstring to `doc` attribute
# once we upgrade to stardoc. Skydoc crashes with
# ```
# File "internal/npm_package/npm_package.bzl", line 221, in <module>
# outputs = NPM_PACKAGE_OUTPUTS,
# TypeError: rule() got an unexpected keyword argument 'doc'
# ```
# when it encounters a `doc` attribute in a rule.
Loading

0 comments on commit 72f2df5

Please sign in to comment.