forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ivy): support ng-add in localize package (angular#32791)
PR Close angular#32791
- Loading branch information
1 parent
84feda1
commit e41cbfb
Showing
11 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
load("//tools:defaults.bzl", "npm_package") | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
filegroup( | ||
name = "package_assets", | ||
srcs = [ | ||
"collection.json", | ||
], | ||
visibility = ["//packages/localize:__subpackages__"], | ||
) | ||
|
||
npm_package( | ||
name = "npm_package", | ||
srcs = [ | ||
"collection.json", | ||
], | ||
deps = [ | ||
"//packages/localize/schematics/ng-add", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", | ||
"schematics": { | ||
"ng-add": { | ||
"description": "Add @angular/localize polyfill to a project.", | ||
"factory": "./ng-add", | ||
"schema": "ng-add/schema.json" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") | ||
load("@npm_bazel_typescript//:index.bzl", "ts_config") | ||
|
||
ts_config( | ||
name = "tsconfig", | ||
src = "tsconfig-build.json", | ||
deps = ["//packages:tsconfig-build.json"], | ||
) | ||
|
||
ts_library( | ||
name = "ng-add", | ||
srcs = [ | ||
"index.ts", | ||
"schema.d.ts", | ||
], | ||
data = glob(["files/**/*"]) + [ | ||
"schema.json", | ||
], | ||
tsconfig = ":tsconfig", | ||
deps = [ | ||
"@npm//@angular-devkit/core", | ||
"@npm//@angular-devkit/schematics", | ||
"@npm//@schematics/angular", | ||
], | ||
) | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = [ | ||
"index_spec.ts", | ||
], | ||
data = [ | ||
"//packages/localize/schematics:package_assets", | ||
], | ||
deps = [ | ||
":ng-add", | ||
"@npm//@angular-devkit/schematics", | ||
], | ||
) | ||
|
||
jasmine_node_test( | ||
name = "test", | ||
bootstrap = ["angular/tools/testing/init_node_spec.js"], | ||
deps = [ | ||
":test_lib", | ||
"//tools/testing:node", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# @angular/localize schematic for `ng add` | ||
|
||
This schematic will be executed when a Angular CLI user runs `ng add @angular/localize`. | ||
|
||
It will search their `angular.json` file, and find polyfills and main files for server builders. | ||
Then it will add the `@angular/localize/init` polyfill that `@angular/localize` needs to work. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* @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 | ||
* | ||
* @fileoverview Schematics for ng-new project that builds with Bazel. | ||
*/ | ||
|
||
import {virtualFs} from '@angular-devkit/core'; | ||
import {Rule, Tree, chain} from '@angular-devkit/schematics'; | ||
import {getWorkspace} from '@schematics/angular/utility/config'; | ||
import {getProjectTargets} from '@schematics/angular/utility/project-targets'; | ||
import {validateProjectName} from '@schematics/angular/utility/validation'; | ||
import {BrowserBuilderTarget, Builders, ServeBuilderTarget} from '@schematics/angular/utility/workspace-models'; | ||
|
||
import {Schema} from './schema'; | ||
|
||
|
||
export const localizePolyfill = `import '@angular/localize/init';`; | ||
|
||
function getAllOptionValues<T>( | ||
host: Tree, projectName: string, builderName: string, optionName: string) { | ||
const targets = getProjectTargets(host, projectName); | ||
|
||
// Find all targets of a specific build in a project. | ||
const builderTargets: (BrowserBuilderTarget | ServeBuilderTarget)[] = | ||
Object.values(targets).filter( | ||
(target: BrowserBuilderTarget | ServeBuilderTarget) => target.builder === builderName); | ||
|
||
// Get all options contained in target configuration partials. | ||
const configurationOptions = builderTargets.filter(t => t.configurations) | ||
.map(t => Object.values(t.configurations !)) | ||
.reduce((acc, cur) => acc.concat(...cur), []); | ||
|
||
// Now we have all option sets. We can use it to find all references to a given property. | ||
const allOptions = [ | ||
...builderTargets.map(t => t.options), | ||
...configurationOptions, | ||
]; | ||
|
||
// Get all values for the option name and dedupe them. | ||
// Deduping will only work for primitives, but the keys we want here are strings so it's ok. | ||
const optionValues: T[] = | ||
allOptions.filter(o => o[optionName]) | ||
.map(o => o[optionName]) | ||
.reduce((acc, cur) => !acc.includes(cur) ? acc.concat(cur) : acc, []); | ||
|
||
return optionValues; | ||
} | ||
|
||
|
||
function prendendToTargetOptionFile( | ||
projectName: string, builderName: string, optionName: string, str: string) { | ||
return (host: Tree) => { | ||
// Get all known polyfills for browser builders on this project. | ||
const optionValues = getAllOptionValues<string>(host, projectName, builderName, optionName); | ||
|
||
optionValues.forEach(path => { | ||
const data = host.read(path); | ||
if (!data) { | ||
// If the file doesn't exist, just ignore it. | ||
return; | ||
} | ||
|
||
const content = virtualFs.fileBufferToString(data); | ||
if (content.includes(localizePolyfill) || | ||
content.includes(localizePolyfill.replace(/'/g, '"'))) { | ||
// If the file already contains the polyfill (or variations), ignore it too. | ||
return; | ||
} | ||
|
||
// Add string at the start of the file. | ||
const recorder = host.beginUpdate(path); | ||
recorder.insertLeft(0, str); | ||
host.commitUpdate(recorder); | ||
}); | ||
}; | ||
} | ||
|
||
export default function(options: Schema): Rule { | ||
return (host: Tree) => { | ||
options.name = options.name || getWorkspace(host).defaultProject; | ||
if (!options.name) { | ||
throw new Error('Please specify a project using "--name project-name"'); | ||
} | ||
validateProjectName(options.name); | ||
|
||
const localizeStr = | ||
`/*************************************************************************************************** | ||
* Load \`$localize\` onto the global scope - used if i18n tags appear in Angular templates. | ||
*/ | ||
${localizePolyfill} | ||
`; | ||
|
||
return chain([ | ||
prendendToTargetOptionFile(options.name, Builders.Browser, 'polyfills', localizeStr), | ||
prendendToTargetOptionFile(options.name, Builders.Server, 'main', localizeStr), | ||
]); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/** | ||
* @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 {HostTree} from '@angular-devkit/schematics'; | ||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; | ||
|
||
import {localizePolyfill} from './index'; | ||
|
||
|
||
describe('ng-add schematic', () => { | ||
|
||
const countInstances = (str: string, substr: string) => str.split(substr).length - 1; | ||
const defaultOptions = {name: 'demo'}; | ||
let host: UnitTestTree; | ||
let schematicRunner: SchematicTestRunner; | ||
// The real polyfills file is bigger than this, but for the test it shouldn't matter. | ||
const polyfillsContent = | ||
`/*************************************************************************************************** | ||
* Zone JS is required by default for Angular itself. | ||
*/ | ||
import 'zone.js/dist/zone';`; | ||
const mainServerContent = `import { enableProdMode } from '@angular/core'; | ||
import { environment } from './environments/environment'; | ||
if (environment.production) { | ||
enableProdMode(); | ||
} | ||
export { AppServerModule } from './app/app.server.module'; | ||
export { renderModule, renderModuleFactory } from '@angular/platform-server';`; | ||
|
||
beforeEach(() => { | ||
host = new UnitTestTree(new HostTree()); | ||
host.create('src/polyfills.ts', polyfillsContent); | ||
host.create('src/another-polyfills.ts', polyfillsContent); | ||
host.create('src/unrelated-polyfills.ts', polyfillsContent); | ||
host.create('src/another-unrelated-polyfills.ts', polyfillsContent); | ||
host.create('src/main.server.ts', mainServerContent); | ||
host.create('src/another-main.server.ts', mainServerContent); | ||
host.create('src/unrelated-main.server.ts', mainServerContent); | ||
host.create('src/another-unrelated-main.server.ts', mainServerContent); | ||
host.create('angular.json', JSON.stringify({ | ||
projects: { | ||
'demo': { | ||
architect: { | ||
build: { | ||
builder: '@angular-devkit/build-angular:browser', | ||
options: { | ||
polyfills: 'src/polyfills.ts', | ||
}, | ||
configurations: { | ||
production: { | ||
polyfills: 'src/another-polyfills.ts', | ||
} | ||
} | ||
}, | ||
'another-build': { | ||
builder: '@angular-devkit/build-angular:browser', | ||
options: { | ||
polyfills: 'src/polyfills.ts', | ||
}, | ||
configurations: { | ||
production: { | ||
polyfills: 'src/another-polyfills.ts', | ||
} | ||
} | ||
}, | ||
server: { | ||
builder: '@angular-devkit/build-angular:server', | ||
options: { | ||
main: 'src/main.server.ts', | ||
}, | ||
configurations: { | ||
production: { | ||
main: 'src/another-main.server.ts', | ||
} | ||
} | ||
}, | ||
'another-server': { | ||
builder: '@angular-devkit/build-angular:server', | ||
options: { | ||
main: 'src/main.server.ts', | ||
}, | ||
configurations: { | ||
production: { | ||
main: 'src/another-main.server.ts', | ||
} | ||
} | ||
}, | ||
'not-browser-or-server': { | ||
builder: '@angular-devkit/build-angular:something-else', | ||
options: { | ||
polyfills: 'src/unrelated-polyfills.ts', | ||
main: 'src/unrelated-main.server.ts', | ||
}, | ||
configurations: { | ||
production: { | ||
polyfills: 'src/other-unrelated-polyfills.ts', | ||
main: 'src/another-unrelated-main.server.ts', | ||
} | ||
} | ||
}, | ||
}, | ||
} | ||
}, | ||
defaultProject: 'demo', | ||
})); | ||
schematicRunner = | ||
new SchematicTestRunner('@angular/localize', require.resolve('../collection.json')); | ||
}); | ||
|
||
it('should add localize polyfill to polyfill files', async() => { | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
expect(host.readContent('/src/polyfills.ts')).toContain(localizePolyfill); | ||
expect(host.readContent('/src/another-polyfills.ts')).toContain(localizePolyfill); | ||
}); | ||
|
||
it('should add localize polyfill to server main files', async() => { | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
expect(host.readContent('/src/main.server.ts')).toContain(localizePolyfill); | ||
expect(host.readContent('/src/another-main.server.ts')).toContain(localizePolyfill); | ||
}); | ||
|
||
it('should add localize polyfill at the start of file', async() => { | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
const content = host.readContent('/src/polyfills.ts'); | ||
expect(content.indexOf(localizePolyfill)).toBeLessThan(content.indexOf(polyfillsContent)); | ||
}); | ||
|
||
it('should not add localize polyfill to files referenced in other targets files', async() => { | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
expect(host.readContent('/src/unrelated-polyfills.ts')).not.toContain(localizePolyfill); | ||
expect(host.readContent('/src/another-unrelated-polyfills.ts')).not.toContain(localizePolyfill); | ||
expect(host.readContent('/src/unrelated-main.server.ts')).not.toContain(localizePolyfill); | ||
expect(host.readContent('/src/another-unrelated-main.server.ts')) | ||
.not.toContain(localizePolyfill); | ||
}); | ||
|
||
it('should only add localize polyfill once if multiple builds reference it', async() => { | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
const content = host.readContent('/src/polyfills.ts'); | ||
expect(countInstances(content, localizePolyfill)).toBe(1); | ||
}); | ||
|
||
it('should not add localize polyfill if it\'s already there', async() => { | ||
const polyfillVariation = localizePolyfill.replace(/'/g, '"'); | ||
host.overwrite('/src/polyfills.ts', `${localizePolyfill}\n${polyfillsContent}`); | ||
host.overwrite('/src/another-polyfills.ts', `${polyfillVariation}\n${polyfillsContent}`); | ||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
expect(countInstances(host.readContent('/src/polyfills.ts'), localizePolyfill)).toBe(1); | ||
expect(countInstances(host.readContent('/src/another-polyfills.ts'), localizePolyfill)).toBe(0); | ||
}); | ||
|
||
it('should not break when there are no polyfills', async() => { | ||
host.overwrite('angular.json', JSON.stringify({ | ||
projects: { | ||
'demo': { | ||
architect: {}, | ||
} | ||
}, | ||
defaultProject: 'demo', | ||
})); | ||
await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* @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 | ||
*/ | ||
|
||
export interface Schema { | ||
/** | ||
* The name of the project. | ||
*/ | ||
name?: string; | ||
} |
Oops, something went wrong.