Skip to content

Commit 6b3aa60

Browse files
petebacondarwinkara
authored andcommitted
fix(ngcc): support simple browser property in entry-points (#36396)
The `browser` package.json property is now supported to the same level as `main` - i.e. it is sniffed for UMD, ESM5 and CommonJS. The `browser` property can also contain an object with file overrides but this is not supported by ngcc. Fixes #36062 PR Close #36396
1 parent 2463548 commit 6b3aa60

File tree

3 files changed

+64
-44
lines changed

3 files changed

+64
-44
lines changed

packages/compiler-cli/ngcc/src/packages/entry_point.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface JsonObject {
5050
}
5151

5252
export interface PackageJsonFormatPropertiesMap {
53+
browser?: string;
5354
fesm2015?: string;
5455
fesm5?: string;
5556
es2015?: string; // if exists then it is actually FESM2015
@@ -75,7 +76,7 @@ export interface EntryPointPackageJson extends JsonObject, PackageJsonFormatProp
7576
export type EntryPointJsonProperty = Exclude<PackageJsonFormatProperties, 'types'|'typings'>;
7677
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
7778
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
78-
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
79+
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module', 'browser'];
7980

8081

8182
/**
@@ -193,13 +194,18 @@ export function getEntryPointFormat(
193194
return 'esm2015';
194195
case 'esm5':
195196
return 'esm5';
197+
case 'browser':
198+
const browserFile = entryPoint.packageJson['browser'];
199+
if (typeof browserFile !== 'string') {
200+
return undefined;
201+
}
202+
return sniffModuleFormat(fs, join(entryPoint.path, browserFile));
196203
case 'main':
197204
const mainFile = entryPoint.packageJson['main'];
198205
if (mainFile === undefined) {
199206
return undefined;
200207
}
201-
const pathToMain = join(entryPoint.path, mainFile);
202-
return sniffModuleFormat(fs, pathToMain);
208+
return sniffModuleFormat(fs, join(entryPoint.path, mainFile));
203209
case 'module':
204210
return 'esm5';
205211
default:

packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ runInEachFileSystem(() => {
793793
const propertiesToConsider = ['es1337', 'fesm42'];
794794
const errorMessage =
795795
'No supported format property to consider among [es1337, fesm42]. Supported ' +
796-
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module';
796+
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module, browser';
797797

798798
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider}))
799799
.toThrowError(errorMessage);

packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '../../../
1010
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
1111
import {loadTestFiles} from '../../../test/helpers';
1212
import {NgccConfiguration} from '../../src/packages/configuration';
13-
import {EntryPoint, getEntryPointFormat, getEntryPointInfo, INCOMPATIBLE_ENTRY_POINT, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point';
13+
import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat, getEntryPointInfo, INCOMPATIBLE_ENTRY_POINT, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point';
1414
import {MockLogger} from '../helpers/mock_logger';
1515

1616
runInEachFileSystem(() => {
@@ -206,7 +206,7 @@ runInEachFileSystem(() => {
206206

207207
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
208208
// Ignore the UMD format
209-
if (prop === 'main') continue;
209+
if (prop === 'main' || prop === 'browser') continue;
210210
// Let's give 'module' a specific path, otherwise compute it based on the property.
211211
const typingsPath = prop === 'module' ? 'index' : `${prop}/missing_typings`;
212212

@@ -425,67 +425,80 @@ runInEachFileSystem(() => {
425425
expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm5');
426426
});
427427

428-
it('should return `esm5` for `main` if the file contains import or export statements', () => {
429-
const name = _(
430-
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js');
431-
loadTestFiles([{name, contents: `import * as core from '@angular/core;`}]);
432-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('esm5');
428+
(['browser', 'main'] as EntryPointJsonProperty[]).forEach(browserOrMain => {
429+
it('should return `esm5` for `' + browserOrMain +
430+
'` if the file contains import or export statements',
431+
() => {
432+
const name = _(
433+
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js');
434+
loadTestFiles([{name, contents: `import * as core from '@angular/core;`}]);
435+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
433436

434-
loadTestFiles([{name, contents: `import {Component} from '@angular/core;`}]);
435-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('esm5');
437+
loadTestFiles([{name, contents: `import {Component} from '@angular/core;`}]);
438+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
436439

437-
loadTestFiles([{name, contents: `export function foo() {}`}]);
438-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('esm5');
440+
loadTestFiles([{name, contents: `export function foo() {}`}]);
441+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
439442

440-
loadTestFiles([{name, contents: `export * from 'abc';`}]);
441-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('esm5');
442-
});
443+
loadTestFiles([{name, contents: `export * from 'abc';`}]);
444+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
445+
});
443446

444-
it('should return `umd` for `main` if the file contains a UMD wrapper function', () => {
445-
loadTestFiles([{
446-
name: _(
447-
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
448-
contents: `
447+
it('should return `umd` for `' + browserOrMain +
448+
'` if the file contains a UMD wrapper function',
449+
() => {
450+
loadTestFiles([{
451+
name: _(
452+
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
453+
contents: `
449454
(function (global, factory) {
450455
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
451456
typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) :
452457
(global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core));
453458
}(this, function (exports, core) { 'use strict'; }));
454459
`
455-
}]);
456-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd');
457-
});
460+
}]);
461+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
462+
});
458463

459-
it('should return `commonjs` for `main` if the file does not contain a UMD wrapper function', () => {
460-
loadTestFiles([{
461-
name: _(
462-
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
463-
contents: `
464+
it('should return `commonjs` for `' + browserOrMain +
465+
'` if the file does not contain a UMD wrapper function',
466+
() => {
467+
loadTestFiles([{
468+
name: _(
469+
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
470+
contents: `
464471
const core = require('@angular/core);
465472
module.exports = {};
466473
`
467-
}]);
468-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('commonjs');
469-
});
474+
}]);
475+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('commonjs');
476+
});
470477

471-
it('should resolve the format path with suitable postfixes', () => {
472-
loadTestFiles([{
473-
name: _(
474-
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
475-
contents: `
478+
it('should resolve the format path with suitable postfixes', () => {
479+
loadTestFiles([{
480+
name: _(
481+
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
482+
contents: `
476483
(function (global, factory) {
477484
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
478485
typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) :
479486
(global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core));
480487
}(this, function (exports, core) { 'use strict'; }));
481488
`
482-
}]);
489+
}]);
490+
491+
entryPoint.packageJson.main = './bundles/valid_entry_point/index';
492+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
483493

484-
entryPoint.packageJson.main = './bundles/valid_entry_point/index';
485-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd');
494+
entryPoint.packageJson.main = './bundles/valid_entry_point';
495+
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
496+
});
497+
});
486498

487-
entryPoint.packageJson.main = './bundles/valid_entry_point';
488-
expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd');
499+
it('should return `undefined` if the `browser` property is not a string', () => {
500+
entryPoint.packageJson.browser = {} as any;
501+
expect(getEntryPointFormat(fs, entryPoint, 'browser')).toBeUndefined();
489502
});
490503
});
491504
});
@@ -503,6 +516,7 @@ export function createPackageJson(
503516
fesm5: `./fesm5/${packageName}.js`,
504517
esm5: `./esm5/${packageName}.js`,
505518
main: `./bundles/${packageName}/index.js`,
519+
browser: `./bundles/${packageName}/index.js`,
506520
module: './index.js',
507521
};
508522
if (excludes) {

0 commit comments

Comments
 (0)