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.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.