6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { runTargetSpec } from '@angular-devkit/architect/testing' ;
9
+ import { DefaultTimeout , TestLogger , runTargetSpec } from '@angular-devkit/architect/testing' ;
10
10
import { join , virtualFs } from '@angular-devkit/core' ;
11
- import { tap } from 'rxjs/operators' ;
11
+ import { debounceTime , takeWhile , tap } from 'rxjs/operators' ;
12
12
import { browserTargetSpec , host , outputPath } from '../utils' ;
13
13
14
14
15
15
describe ( 'Browser Builder bundle worker' , ( ) => {
16
16
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 ) ) ;
18
18
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' : `
22
22
import { foo } from './dep';
23
-
24
23
console.log('hello from worker');
25
-
26
24
addEventListener('message', ({ data }) => {
27
25
console.log('worker got message:', data);
28
26
if (data === 'hello') {
@@ -31,61 +29,187 @@ describe('Browser Builder bundle worker', () => {
31
29
});
32
30
` ,
33
31
'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' });
35
40
worker.onmessage = ({ data }) => {
36
41
console.log('page got message:', data);
37
42
};
38
43
worker.postMessage('hello');
39
44
` ,
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
+ }` ,
40
77
} ;
41
78
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
+ ) ;
90
214
} ) ;
91
215
} ) ;
0 commit comments