feat(bazel): add an ng_package rule (#22221)

This produces a directory following the Angular Package layout spec.

Includes integration test coverage by making a minimal ng_package in integration/bazel.
Unit tests verify the content of the @angular/core and @angular/common packages.

This doesn't totally match our current output, but is good enough to unblock some
early adopters.

It re-uses logic from the rollup_bundle rule in rules_nodejs. It should also
eventually have the .pack and .publish secondary targets like npm_package rule.

PR Close #22221
This commit is contained in:
Alex Eagle
2018-02-13 11:26:06 -08:00
committed by Victor Berchet
parent 1dcbc12fd3
commit b43b164a61
23 changed files with 1101 additions and 61 deletions

View File

@ -0,0 +1,36 @@
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
exports_files(["package.json"])
# The tests in this package must run in separate targets, since they change
# working directory and therefore have mutable global state that causes test
# isolation failures.
ts_library(
name = "core_spec_lib",
testonly = True,
srcs = ["core_package.spec.ts"],
deps = ["//packages:types"],
)
jasmine_node_test(
name = "core_package",
srcs = [":core_spec_lib"],
data = [
"//packages/core:npm_package",
],
)
ts_library(
name = "common_spec_lib",
testonly = True,
srcs = ["common_package.spec.ts"],
deps = ["//packages:types"],
)
jasmine_node_test(
name = "common_package",
srcs = [":common_spec_lib"],
data = ["//packages/common:npm_package"],
)

View File

@ -0,0 +1,50 @@
/**
* @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
*/
import * as path from 'path';
import * as shx from 'shelljs';
shx.cd(path.join(process.env['TEST_SRCDIR'], 'angular', 'packages', 'common', 'npm_package'));
describe('ng_package', () => {
it('should have right bundle files', () => {
expect(shx.ls('-R', 'bundles').stdout.split('\n').filter(n => !!n).sort()).toEqual([
'common-http-testing.umd.js',
'common-http-testing.umd.js.map',
'common-http-testing.umd.min.js',
'common-http-testing.umd.min.js.map',
'common-http.umd.js',
'common-http.umd.js.map',
'common-http.umd.min.js',
'common-http.umd.min.js.map',
'common-testing.umd.js',
'common-testing.umd.js.map',
'common-testing.umd.min.js',
'common-testing.umd.min.js.map',
'common.umd.js',
'common.umd.js.map',
'common.umd.min.js',
'common.umd.min.js.map',
]);
});
it('should have right fesm files', () => {
const expected = [
'common.js',
'common.js.map',
'http',
'http.js',
'http.js.map',
'http/testing.js',
'http/testing.js.map',
'testing.js',
'testing.js.map',
];
expect(shx.ls('-R', 'esm5').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected);
expect(shx.ls('-R', 'esm2015').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected);
});
});

View File

@ -0,0 +1,227 @@
/**
* @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
*/
import * as path from 'path';
import * as shx from 'shelljs';
const corePackagePath =
path.join(process.env['TEST_SRCDIR'], 'angular', 'packages', 'core', 'npm_package');
shx.cd(corePackagePath);
/**
* Utility functions that allows me to create fs paths
* p`${foo}/some/${{bar}}/path` rather than path.join(foo, 'some',
*/
function p(templateStringArray: TemplateStringsArray) {
const segments = [];
for (const entry of templateStringArray) {
segments.push(...entry.split('/').filter(s => s !== ''));
}
return path.join(...segments);
}
describe('ng_package', () => {
describe('misc root files', () => {
describe('README.md', () => {
it('should have a README.md file with basic info', () => {
expect(shx.cat('README.md')).toContain(`Angular`);
expect(shx.cat('README.md')).toContain(`https://github.com/angular/angular`);
});
});
});
describe('primary entry-point', () => {
describe('package.json', () => {
const packageJson = 'package.json';
it('should have a package.json file',
() => { expect(shx.grep('"name":', packageJson)).toContain(`@angular/core`); });
it('should contain correct version number with the PLACEHOLDER string replaced', () => {
expect(shx.grep('"version":', packageJson)).toMatch(/\d+\.\d+\.\d+(?!-PLACEHOLDER)/);
});
it('should contain module resolution mappings', () => {
const packageJson = 'package.json';
expect(shx.grep('"main":', packageJson)).toContain(`./bundles/core.umd.js`);
expect(shx.grep('"module":', packageJson)).toContain(`./esm5/core.js`);
expect(shx.grep('"es2015":', packageJson)).toContain(`./esm2015/core.js`);
expect(shx.grep('"typings":', packageJson)).toContain(`./core.d.ts`);
});
});
describe('typescript support', () => {
it('should have an index.d.ts file',
() => { expect(shx.cat('core.d.ts')).toContain(`export *`); });
it('should not have amd module names',
() => { expect(shx.cat('public_api.d.ts')).not.toContain('<amd-module name'); });
});
describe('closure', () => {
it('should contain externs', () => {
expect(shx.cat('src/testability/testability.externs.js')).toContain('/** @externs */');
});
});
describe('angular metadata', () => {
it('should have metadata.json files',
() => { expect(shx.cat('core.metadata.json')).toContain(`"__symbolic":"module"`); });
});
describe('fesm15', () => {
it('should have a fesm15 file in the /esm2015 directory',
() => { expect(shx.cat('esm2015/core.js')).toContain(`export {`); });
it('should have a source map', () => {
expect(shx.cat('esm2015/core.js.map'))
.toContain(`{"version":3,"file":"core.js","sources":`);
});
it('should have the version info in the header', () => {
expect(shx.cat('esm2015/core.js'))
.toMatch(/@license Angular v\d+\.\d+\.\d+(?!-PLACEHOLDER)/);
});
});
describe('fesm5', () => {
it('should have a fesm5 file in the /esm5 directory',
() => { expect(shx.cat('esm5/core.js')).toContain(`export {`); });
it('should have a source map', () => {
expect(shx.cat('esm5/core.js.map')).toContain(`{"version":3,"file":"core.js","sources":`);
});
it('should not be processed by tsickle', () => {
expect(shx.cat('esm5/core.js')).not.toContain('@fileoverview added by tsickle');
});
});
describe('umd', () => {
it('should have a umd file in the /bundles directory',
() => { expect(shx.ls('bundles/core.umd.js').length).toBe(1, 'File not found'); });
it('should have a source map next to the umd file',
() => { expect(shx.ls('bundles/core.umd.js.map').length).toBe(1, 'File not found'); });
it('should have a minified umd file in the /bundles directory',
() => { expect(shx.ls('bundles/core.umd.min.js').length).toBe(1, 'File not found'); });
it('should have a source map next to the minified umd file',
() => { expect(shx.ls('bundles/core.umd.min.js.map').length).toBe(1, 'File not found'); });
});
});
describe('secondary entry-point', () => {
describe('package.json', () => {
const packageJson = p `testing/package.json`;
it('should have a package.json file',
() => { expect(shx.grep('"name":', packageJson)).toContain(`@angular/core/testing`); });
it('should have its module resolution mappings defined in the nested package.json', () => {
const packageJson = p `testing/package.json`;
expect(shx.grep('"main":', packageJson)).toContain(`../bundles/core-testing.umd.js`);
expect(shx.grep('"module":', packageJson)).toContain(`../esm5/testing.js`);
expect(shx.grep('"es2015":', packageJson)).toContain(`../esm2015/testing.js`);
expect(shx.grep('"typings":', packageJson)).toContain(`./testing.d.ts`);
});
});
describe('typings', () => {
const typingsFile = p `testing/testing.d.ts`;
it('should have a typings file',
() => { expect(shx.cat(typingsFile)).toContain('export * from \'./public_api\';'); });
});
describe('typescript support', () => {
// TODO(i): why in the parent dir?
it('should have an \'redirect\' d.ts file in the parent dir',
() => { expect(shx.cat('testing.d.ts')).toContain(`export *`); });
it('should have a \'actual\' d.ts file in the parent dir', () => {
expect(shx.cat('testing/testing.d.ts')).toContain(`export * from './public_api';`);
});
});
describe('angular metadata file', () => {
it('should have a \'redirect\' metadata.json file next to the d.ts file', () => {
expect(shx.cat('testing.metadata.json'))
.toContain(`"exports":[{"from":"./testing/testing"}],"flatModuleIndexRedirect":true`);
});
it('should have an \'actual\' metadata.json file', () => {
expect(shx.cat('testing/testing.metadata.json'))
.toContain(`"metadata":{"async":{"__symbolic":"function"},`);
});
});
describe('fesm15', () => {
it('should have a fesm15 file in the /esm2015 directory',
() => { expect(shx.cat('esm2015/testing.js')).toContain(`export {`); });
it('should have a source map', () => {
expect(shx.cat('esm2015/testing.js.map'))
.toContain(`{"version":3,"file":"testing.js","sources":`);
});
it('should have the version info in the header', () => {
expect(shx.cat('esm2015/testing.js'))
.toMatch(/@license Angular v\d+\.\d+\.\d+(?!-PLACEHOLDER)/);
});
});
describe('fesm5', () => {
it('should have a fesm5 file in the /esm5 directory',
() => { expect(shx.cat('esm5/testing.js')).toContain(`export {`); });
it('should have a source map', () => {
expect(shx.cat('esm5/testing.js.map'))
.toContain(`{"version":3,"file":"testing.js","sources":`);
});
});
describe('umd', () => {
it('should have a umd file in the /bundles directory',
() => { expect(shx.ls('bundles/core-testing.umd.js').length).toBe(1, 'File not found'); });
it('should have a source map next to the umd file', () => {
expect(shx.ls('bundles/core-testing.umd.js.map').length).toBe(1, 'File not found');
});
it('should have a minified umd file in the /bundles directory', () => {
expect(shx.ls('bundles/core-testing.umd.min.js').length).toBe(1, 'File not found');
});
it('should have a source map next to the minified umd file', () => {
expect(shx.ls('bundles/core-testing.umd.min.js.map').length).toBe(1, 'File not found');
});
});
});
});