Skip to content
Permalink
Browse files

feat(bazel): add dts bundler as action to ng_module (#28588)

This enabled dts flattening in the final distrubutable package.

Notes:
 - For the time being this is an opt-in feature via the `ng_module` attribute  `bundle_dts`, however in the near future this will be turned on by default.
 - This only supports the legacy compiler `ngc`, as `ngtsc` emits namespaced imports `import * as __` from local modules which is not supported for the time being by API Extractor. See: Microsoft/web-build-tools#1029

Ref: TOOL-611

PR Close #28588
  • Loading branch information...
alan-agius4 authored and mhevery committed Feb 7, 2019
1 parent 3842dd6 commit 3d39100c85d3ea126db3d6d1f04c73fe74d9ce94
@@ -37,6 +37,7 @@
"@angular-devkit/schematics": "^7.3.0-rc.0",
"@bazel/karma": "0.23.2",
"@bazel/typescript": "0.23.2",
"@microsoft/api-extractor": "^7.0.11",
"@schematics/angular": "^7.0.4",
"@types/angular": "^1.6.47",
"@types/base64-js": "1.2.5",
@@ -16,6 +16,7 @@
"@angular-devkit/core": "^7.0.4",
"@angular-devkit/schematics": "^7.3.0-rc.0",
"@bazel/typescript": "^0.23.2",
"@microsoft/api-extractor": "^7.0.11",
"@schematics/angular": "^7.0.4",
"@types/node": "6.0.84",
"semver": "^5.6.0",
@@ -0,0 +1,27 @@
package(default_visibility = ["//packages:__subpackages__"])

load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
load("//tools:defaults.bzl", "ts_library")

ts_library(
name = "lib",
srcs = [
"index.ts",
],
deps = [
"@ngdeps//@bazel/typescript",
"@ngdeps//@microsoft/api-extractor",
"@ngdeps//@types/node",
],
)

nodejs_binary(
name = "api_extractor",
data = [
":lib",
"@ngdeps//@bazel/typescript",
"@ngdeps//@microsoft/api-extractor",
],
entry_point = "angular/packages/bazel/src/api-extractor/index.js",
visibility = ["//visibility:public"],
)
@@ -0,0 +1,115 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/// <reference types="node"/>
/// <reference lib="es2017"/>

import {format, parseTsconfig} from '@bazel/typescript';
import {Extractor, ExtractorValidationRulePolicy, IExtractorConfig, IExtractorOptions} from '@microsoft/api-extractor';
import * as fs from 'fs';
import * as path from 'path';

const DEBUG = false;

export function runMain(
tsConfig: string, entryPoint: string, dtsBundleOut?: string, apiReviewFolder?: string,
acceptApiUpdates = false): 1|0 {
const [parsedConfig, errors] = parseTsconfig(tsConfig);
if (errors && errors.length) {
console.error(format('', errors));

return 1;
}

const pkgJson = path.resolve(path.dirname(entryPoint), 'package.json');
if (!fs.existsSync(pkgJson)) {
fs.writeFileSync(pkgJson, JSON.stringify({
'name': 'GENERATED-BY-BAZEL',
'version': '0.0.0',
'description': 'This is a dummy package.json as API Extractor always requires one.',
}));
}

// API extractor doesn't always support the version of TypeScript used in the repo
// example: at the moment it is not compatable with 3.2
// to use the internal TypeScript we shall not create a program but rather pass a parsed tsConfig.
const parsedTsConfig = parsedConfig !.config as any;
const compilerOptions = parsedTsConfig.compilerOptions;
for (const [key, values] of Object.entries<string[]>(compilerOptions.paths)) {
if (key === '*') {
continue;
}

// we shall not pass ts files as this will need to be parsed, and for example rxjs,
// cannot be compiled with our tsconfig, as ours is more strict
// hence amend the paths to point always to the '.d.ts' files.
compilerOptions.paths[key] = values.map(path => {
const pathSuffix = /(\*|index)$/.test(path) ? '.d.ts' : '/index.d.ts';

return path + pathSuffix;
});
}

const extractorOptions: IExtractorOptions = {
localBuild: acceptApiUpdates,
customLogger: DEBUG ? undefined : {
// don't log verbose messages when not in debug mode
logVerbose: _message => {}
}
};

const extractorConfig: IExtractorConfig = {
compiler: {
configType: 'tsconfig',
overrideTsconfig: parsedTsConfig,
rootFolder: path.resolve(path.dirname(tsConfig))
},
project: {
entryPointSourceFile: path.resolve(entryPoint),
},
apiReviewFile: {
enabled: !!apiReviewFolder,
apiReviewFolder: apiReviewFolder && path.resolve(apiReviewFolder),
},
apiJsonFile: {
enabled: false,
},
policies: {
namespaceSupport: 'permissive',
},
validationRules: {
missingReleaseTags: ExtractorValidationRulePolicy.allow,
},
dtsRollup: {
enabled: !!dtsBundleOut,
publishFolder: dtsBundleOut && path.resolve(path.dirname(dtsBundleOut)),
mainDtsRollupPath: dtsBundleOut && path.basename(dtsBundleOut),
}
};

const extractor = new Extractor(extractorConfig, extractorOptions);
const isSuccessful = extractor.processProject();

// API extractor errors are emitted by it's logger.
return isSuccessful ? 0 : 1;
}

// Entry point
if (require.main === module) {
if (DEBUG) {
console.error(`
api-extractor: running with
cwd: ${process.cwd()}
argv:
${process.argv.join('\n ')}
`);
}

const [tsConfig, entryPoint, dtsBundleOut] = process.argv.slice(2);
process.exitCode = runMain(tsConfig, entryPoint, dtsBundleOut);
}
@@ -30,3 +30,4 @@ ts_providers_dict_to_struct = _ts_providers_dict_to_struct

DEFAULT_NG_COMPILER = "@angular//:@angular/bazel/ngc-wrapped"
DEFAULT_NG_XI18N = "@npm//@angular/bazel/bin:xi18n"
FLAT_DTS_FILE_SUFFIX = ".bundle.d.ts"
@@ -12,6 +12,7 @@ load(
"DEFAULT_NG_COMPILER",
"DEFAULT_NG_XI18N",
"DEPS_ASPECTS",
"FLAT_DTS_FILE_SUFFIX",
"NodeModuleInfo",
"collect_node_modules_aspect",
"compile_ts",
@@ -121,6 +122,24 @@ def _flat_module_out_file(ctx):
return ctx.attr.flat_module_out_file
return "%s_public_index" % ctx.label.name

def _should_produce_dts_bundle(ctx):
"""Should we produce dts bundles.
We only produce flatten dts outs when we expect the ng_module is meant to be published,
based on the value of the bundle_dts attribute.
Args:
ctx: skylark rule execution context
Returns:
true when we should produce bundled dts.
"""

# At the moment we cannot use this with ngtsc compiler since it emits
# import * as ___ from local modules which is not supported
# see: https://github.com/Microsoft/web-build-tools/issues/1029
return _is_legacy_ngc(ctx) and ctx.attr.bundle_dts

def _should_produce_flat_module_outs(ctx):
"""Should we produce flat module outputs.
@@ -200,6 +219,14 @@ def _expected_outs(ctx):
if not _is_bazel():
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]

dts_bundle = None
if _should_produce_dts_bundle(ctx):
# We need to add a suffix to bundle as it might collide with the flat module dts.
# The flat module dts out contains several other exports
# https://github.com/angular/angular/blob/master/packages/compiler-cli/src/metadata/index_writer.ts#L18
# the file name will be like 'core.bundle.d.ts'
dts_bundle = ctx.actions.declare_file(ctx.label.name + FLAT_DTS_FILE_SUFFIX)

# We do this just when producing a flat module index for a publishable ng_module
if _should_produce_flat_module_outs(ctx):
flat_module_out = _flat_module_out_file(ctx)
@@ -225,6 +252,7 @@ def _expected_outs(ctx):
declarations = declaration_files,
summaries = summary_files,
metadata = metadata_files,
dts_bundle = dts_bundle,
bundle_index_typings = bundle_index_typings,
i18n_messages = i18n_messages_files,
)
@@ -302,6 +330,7 @@ def ngc_compile_action(
label,
inputs,
outputs,
dts_bundle_out,
messages_out,
tsconfig_file,
node_opts,
@@ -317,6 +346,7 @@ def ngc_compile_action(
label: the label of the ng_module being compiled
inputs: passed to the ngc action's inputs
outputs: passed to the ngc action's outputs
dts_bundle_out: produced flattened dts file
messages_out: produced xmb files
tsconfig_file: tsconfig file with settings used for the compilation
node_opts: list of strings, extra nodejs options.
@@ -380,6 +410,28 @@ def ngc_compile_action(
mnemonic = "Angular2MessageExtractor",
)

if dts_bundle_out != None:
# combine the inputs and outputs and filter .d.ts and json files
filter_inputs = [f for f in inputs + outputs if f.path.endswith(".d.ts") or f.path.endswith(".json")]

if _should_produce_flat_module_outs(ctx):
dts_entry_point = "%s.d.ts" % _flat_module_out_file(ctx)
else:
dts_entry_point = ctx.attr.entry_point.replace(".ts", ".d.ts")

ctx.actions.run(
progress_message = "Bundling DTS %s" % str(ctx.label),
mnemonic = "APIExtractor",
executable = ctx.executable._api_extractor,
inputs = filter_inputs,
outputs = [dts_bundle_out],
arguments = [
tsconfig_file.path,
"/".join([ctx.bin_dir.path, ctx.label.package, dts_entry_point]),
dts_bundle_out.path,
],
)

if not locale and not ctx.attr.no_i18n:
return struct(
label = label,
@@ -400,7 +452,7 @@ def _filter_ts_inputs(all_inputs):
if f.path.endswith(".js") or f.path.endswith(".ts") or f.path.endswith(".json")
]

def _compile_action(ctx, inputs, outputs, messages_out, tsconfig_file, node_opts):
def _compile_action(ctx, inputs, outputs, dts_bundle_out, messages_out, tsconfig_file, node_opts):
# Give the Angular compiler all the user-listed assets
file_inputs = list(ctx.files.assets)

@@ -427,16 +479,16 @@ def _compile_action(ctx, inputs, outputs, messages_out, tsconfig_file, node_opts
],
)

return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts)
return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, dts_bundle_out, messages_out, tsconfig_file, node_opts)

def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
outs = _expected_outs(ctx)
return _compile_action(ctx, inputs, outputs + outs.closure_js, outs.i18n_messages, tsconfig_file, node_opts)
return _compile_action(ctx, inputs, outputs + outs.closure_js, None, outs.i18n_messages, tsconfig_file, node_opts)

def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
outs = _expected_outs(ctx)
compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
_compile_action(ctx, inputs, compile_action_outputs, None, tsconfig_file, node_opts)
_compile_action(ctx, inputs, compile_action_outputs, outs.dts_bundle, None, tsconfig_file, node_opts)

def _ts_expected_outs(ctx, label, srcs_files = []):
# rules_typescript expects a function with two or more arguments, but our
@@ -497,6 +549,9 @@ def ng_module_impl(ctx, ts_compile_actions):
flat_module_out_file = _flat_module_out_file(ctx),
)

if outs.dts_bundle != None:
providers["dts_bundle"] = outs.dts_bundle

return providers

def _ng_module_impl(ctx):
@@ -551,6 +606,11 @@ NG_MODULE_ATTRIBUTES = {
executable = True,
cfg = "host",
),
"_api_extractor": attr.label(
default = Label("//packages/bazel/src/api-extractor:api_extractor"),
executable = True,
cfg = "host",
),
"_supports_workers": attr.bool(default = True),
}

@@ -630,6 +690,7 @@ NG_MODULE_RULE_ATTRS = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
# See the flatModuleOutFile documentation in
# https://github.com/angular/angular/blob/master/packages/compiler-cli/src/transformers/api.ts
"flat_module_out_file": attr.string(),
"bundle_dts": attr.bool(default = False),
})

ng_module = rule(
Oops, something went wrong.

0 comments on commit 3d39100

Please sign in to comment.
You can’t perform that action at this time.