66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { runTargetSpec } from '@angular-devkit/architect/testing' ;
9+ import { DefaultTimeout , TestLogger , runTargetSpec } from '@angular-devkit/architect/testing' ;
1010import { join , virtualFs } from '@angular-devkit/core' ;
11- import { tap } from 'rxjs/operators' ;
11+ import { debounceTime , takeWhile , tap } from 'rxjs/operators' ;
1212import { browserTargetSpec , host , outputPath } from '../utils' ;
1313
1414
1515describe ( 'Browser Builder bundle worker' , ( ) => {
1616 beforeEach ( done => host . initialize ( ) . toPromise ( ) . then ( done , done . fail ) ) ;
17- // afterEach(done => host.restore().toPromise().then(done, done.fail));
17+ afterEach ( done => host . restore ( ) . toPromise ( ) . then ( done , done . fail ) ) ;
1818
19- const workerFiles = {
20- 'src/dep.js' : `export const foo = 'bar';` ,
21- 'src/worker.js' : `
19+ const workerFiles : { [ k : string ] : string } = {
20+ 'src/app/ dep.js' : `export const foo = 'bar';` ,
21+ 'src/app/app. worker.js' : `
2222 import { foo } from './dep';
23-
2423 console.log('hello from worker');
25-
2624 addEventListener('message', ({ data }) => {
2725 console.log('worker got message:', data);
2826 if (data === 'hello') {
@@ -31,61 +29,187 @@ describe('Browser Builder bundle worker', () => {
3129 });
3230 ` ,
3331 'src/main.ts' : `
34- const worker = new Worker('./worker', { type: 'module' });
32+ import { enableProdMode } from '@angular/core';
33+ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
34+ import { AppModule } from './app/app.module';
35+ import { environment } from './environments/environment';
36+ if (environment.production) { enableProdMode(); }
37+ platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
38+
39+ const worker = new Worker('./app/app.worker.js', { type: 'module' });
3540 worker.onmessage = ({ data }) => {
3641 console.log('page got message:', data);
3742 };
3843 worker.postMessage('hello');
3944 ` ,
45+ // Make a new tsconfig for the *.worker.ts files.
46+ // The final place for this tsconfig must take into consideration editor tooling, unit
47+ // tests, and integration with other build targets.
48+ './src/tsconfig.worker.json' : `
49+ {
50+ "extends": "../tsconfig.json",
51+ "compilerOptions": {
52+ "outDir": "../out-tsc/worker",
53+ "lib": [
54+ "es2018",
55+ "webworker"
56+ ],
57+ "types": []
58+ },
59+ "include": [
60+ "**/*.worker.ts",
61+ ]
62+ }` ,
63+ // Alter the app tsconfig to not include *.worker.ts files.
64+ './src/tsconfig.app.json' : `
65+ {
66+ "extends": "../tsconfig.json",
67+ "compilerOptions": {
68+ "outDir": "../out-tsc/worker",
69+ "types": []
70+ },
71+ "exclude": [
72+ "test.ts",
73+ "**/*.spec.ts",
74+ "**/*.worker.ts",
75+ ]
76+ }` ,
4077 } ;
4178
42- describe ( 'js workers' , ( ) => {
43- it ( 'bundles worker' , ( done ) => {
44- host . writeMultipleFiles ( workerFiles ) ;
45- const overrides = { autoBundleWorkerModules : true } ;
46- runTargetSpec ( host , browserTargetSpec , overrides ) . pipe (
47- tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
48- tap ( ( ) => {
49- const workerContent = virtualFs . fileBufferToString (
50- host . scopedSync ( ) . read ( join ( outputPath , '0.worker.js' ) ) ,
51- ) ;
52- // worker bundle contains worker code.
53- expect ( workerContent ) . toContain ( 'hello from worker' ) ;
54- expect ( workerContent ) . toContain ( 'bar' ) ;
55-
56- const mainContent = virtualFs . fileBufferToString (
57- host . scopedSync ( ) . read ( join ( outputPath , 'main.js' ) ) ,
58- ) ;
59- // main bundle references worker.
60- expect ( mainContent ) . toContain ( '0.worker.js' ) ;
61- } ) ,
62- ) . toPromise ( ) . then ( done , done . fail ) ;
63- } ) ;
64-
65- it ( 'minimizes and hashes worker' , ( done ) => {
66- host . writeMultipleFiles ( workerFiles ) ;
67- const overrides = { autoBundleWorkerModules : true , outputHashing : 'all' , optimization : true } ;
68- runTargetSpec ( host , browserTargetSpec , overrides ) . pipe (
69- tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
70- tap ( ( ) => {
71- const workerBundle = host . fileMatchExists ( outputPath ,
72- / 0 \. [ 0 - 9 a - f ] { 20 } \. w o r k e r \. j s / ) as string ;
73- expect ( workerBundle ) . toBeTruthy ( 'workerBundle should exist' ) ;
74- const workerContent = virtualFs . fileBufferToString (
75- host . scopedSync ( ) . read ( join ( outputPath , workerBundle ) ) ,
76- ) ;
77- expect ( workerContent ) . toContain ( 'hello from worker' ) ;
78- expect ( workerContent ) . toContain ( 'bar' ) ;
79- expect ( workerContent ) . toContain ( '"hello"===e&&postMessage("bar")' ) ;
80-
81- const mainBundle = host . fileMatchExists ( outputPath , / m a i n \. [ 0 - 9 a - f ] { 20 } \. j s / ) as string ;
82- expect ( mainBundle ) . toBeTruthy ( 'mainBundle should exist' ) ;
83- const mainContent = virtualFs . fileBufferToString (
84- host . scopedSync ( ) . read ( join ( outputPath , mainBundle ) ) ,
85- ) ;
86- expect ( mainContent ) . toContain ( workerBundle ) ;
87- } ) ,
88- ) . toPromise ( ) . then ( done , done . fail ) ;
89- } ) ;
79+ // Use the same worker file content but in a .ts file name.
80+ const tsWorkerFiles = Object . keys ( workerFiles )
81+ . reduce ( ( acc , k ) => {
82+ // Replace the .js files with .ts, and also references within the files.
83+ acc [ k . replace ( / \. j s $ / , '.ts' ) ] = workerFiles [ k ] . replace ( / \. j s ' / g, `.ts'` ) ;
84+
85+ return acc ;
86+ } , { } as { [ k : string ] : string } ) ;
87+
88+ it ( 'bundles worker' , ( done ) => {
89+ host . writeMultipleFiles ( workerFiles ) ;
90+ const overrides = { autoBundleWorkerModules : true } ;
91+ runTargetSpec ( host , browserTargetSpec , overrides ) . pipe (
92+ tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
93+ tap ( ( ) => {
94+ const workerContent = virtualFs . fileBufferToString (
95+ host . scopedSync ( ) . read ( join ( outputPath , '0.worker.js' ) ) ,
96+ ) ;
97+ // worker bundle contains worker code.
98+ expect ( workerContent ) . toContain ( 'hello from worker' ) ;
99+ expect ( workerContent ) . toContain ( 'bar' ) ;
100+
101+ const mainContent = virtualFs . fileBufferToString (
102+ host . scopedSync ( ) . read ( join ( outputPath , 'main.js' ) ) ,
103+ ) ;
104+ // main bundle references worker.
105+ expect ( mainContent ) . toContain ( '0.worker.js' ) ;
106+ } ) ,
107+ ) . toPromise ( ) . then ( done , done . fail ) ;
108+ } ) ;
109+
110+ it ( 'minimizes and hashes worker' , ( done ) => {
111+ host . writeMultipleFiles ( workerFiles ) ;
112+ const overrides = { autoBundleWorkerModules : true , outputHashing : 'all' , optimization : true } ;
113+ runTargetSpec ( host , browserTargetSpec , overrides ) . pipe (
114+ tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
115+ tap ( ( ) => {
116+ const workerBundle = host . fileMatchExists ( outputPath ,
117+ / 0 \. [ 0 - 9 a - f ] { 20 } \. w o r k e r \. j s / ) as string ;
118+ expect ( workerBundle ) . toBeTruthy ( 'workerBundle should exist' ) ;
119+ const workerContent = virtualFs . fileBufferToString (
120+ host . scopedSync ( ) . read ( join ( outputPath , workerBundle ) ) ,
121+ ) ;
122+ expect ( workerContent ) . toContain ( 'hello from worker' ) ;
123+ expect ( workerContent ) . toContain ( 'bar' ) ;
124+ expect ( workerContent ) . toContain ( '"hello"===e&&postMessage("bar")' ) ;
125+
126+ const mainBundle = host . fileMatchExists ( outputPath , / m a i n \. [ 0 - 9 a - f ] { 20 } \. j s / ) as string ;
127+ expect ( mainBundle ) . toBeTruthy ( 'mainBundle should exist' ) ;
128+ const mainContent = virtualFs . fileBufferToString (
129+ host . scopedSync ( ) . read ( join ( outputPath , mainBundle ) ) ,
130+ ) ;
131+ expect ( mainContent ) . toContain ( workerBundle ) ;
132+ } ) ,
133+ ) . toPromise ( ) . then ( done , done . fail ) ;
134+ } ) ;
135+
136+ it ( 'bundles TS worker' , ( done ) => {
137+ const logger = new TestLogger ( 'worker-warnings' ) ;
138+ host . writeMultipleFiles ( tsWorkerFiles ) ;
139+ const overrides = {
140+ autoBundleWorkerModules : true ,
141+ workerTsConfig : 'src/tsconfig.worker.json' ,
142+ } ;
143+
144+
145+ runTargetSpec ( host , browserTargetSpec , overrides , DefaultTimeout , logger ) . pipe (
146+ tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
147+ tap ( ( ) => {
148+ const workerContent = virtualFs . fileBufferToString (
149+ host . scopedSync ( ) . read ( join ( outputPath , '0.worker.js' ) ) ,
150+ ) ;
151+ // worker bundle contains worker code.
152+ expect ( workerContent ) . toContain ( 'hello from worker' ) ;
153+ expect ( workerContent ) . toContain ( 'bar' ) ;
154+
155+ const mainContent = virtualFs . fileBufferToString (
156+ host . scopedSync ( ) . read ( join ( outputPath , 'main.js' ) ) ,
157+ ) ;
158+ // main bundle references worker.
159+ expect ( mainContent ) . toContain ( '0.worker.js' ) ;
160+ } ) ,
161+ // Doesn't show any warnings.
162+ tap ( ( ) => expect ( logger . includes ( 'WARNING' ) ) . toBe ( false , 'Should show no warnings.' ) ) ,
163+ ) . toPromise ( ) . then ( done , done . fail ) ;
164+ } ) ;
165+
166+ it ( 'rebuilds TS worker' , ( done ) => {
167+ host . writeMultipleFiles ( tsWorkerFiles ) ;
168+ const overrides = {
169+ autoBundleWorkerModules : true ,
170+ workerTsConfig : 'src/tsconfig.worker.json' ,
171+ watch : true ,
172+ } ;
173+
174+ let buildCount = 0 ;
175+ let phase = 1 ;
176+ const workerPath = join ( outputPath , '0.worker.js' ) ;
177+ let workerContent = '' ;
178+
179+ runTargetSpec ( host , browserTargetSpec , overrides , DefaultTimeout * 3 ) . pipe (
180+ // Note(filipesilva): Wait for files to be written to disk... and something else?
181+ // 1s should be enough for files to be written to disk.
182+ // However, with a 1s to 3s delay, I sometimes (roughly 1 in 3 tests) saw TS compilation
183+ // succeeding and emitting updated content, but the TS loader never called for
184+ // 'src/worker/dep.ts'.
185+ // But increasing this delay to 5s lead to no failed test in over 40 runs.
186+ // I think there might be a race condition related to child compilers somewhere in webpack.
187+ debounceTime ( 5000 ) ,
188+ tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true , 'build should succeed' ) ) ,
189+ tap ( ( ) => {
190+ buildCount ++ ;
191+ switch ( phase ) {
192+ case 1 :
193+ // Original worker content should be there.
194+ workerContent = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( workerPath ) ) ;
195+ expect ( workerContent ) . toContain ( 'bar' ) ;
196+ // Change content of worker dependency.
197+ host . writeMultipleFiles ( { 'src/app/dep.ts' : `export const foo = 'baz';` } ) ;
198+ phase = 2 ;
199+ break ;
200+
201+ case 2 :
202+ // Worker content should have changed.
203+ workerContent = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( workerPath ) ) ;
204+ expect ( workerContent ) . toContain ( 'baz' ) ;
205+ phase = 3 ;
206+ break ;
207+ }
208+ } ) ,
209+ takeWhile ( ( ) => phase < 3 ) ,
210+ ) . toPromise ( ) . then (
211+ ( ) => done ( ) ,
212+ ( ) => done . fail ( `stuck at phase ${ phase } [builds: ${ buildCount } ]` ) ,
213+ ) ;
90214 } ) ;
91215} ) ;
0 commit comments