Skip to content

Commit

Permalink
feat(frontend): add differential loading for web apps
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz authored and vsavkin committed May 23, 2019
1 parent 5f404f4 commit 43af1cd
Show file tree
Hide file tree
Showing 19 changed files with 441 additions and 163 deletions.
48 changes: 32 additions & 16 deletions e2e/schematics/react.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,26 @@ describe('React Applications', () => {
runCLI(`build ${appName}`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/vendor.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/styles.js`
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/vendor-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/styles-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/vendor-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles-es5.js`
);
runCLI(`build ${appName} --prod --output-hashing none`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles.css`
);
const testResults = await runCLIAsync(`test ${appName}`);
Expand Down Expand Up @@ -98,18 +106,26 @@ describe('React Applications', () => {
runCLI(`build ${appName}`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/vendor.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/styles.js`
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/vendor-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/styles-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/vendor-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles-es5.js`
);
runCLI(`build ${appName} --prod --output-hashing none`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles.css`
);
const testResults = await runCLIAsync(`test ${appName}`);
Expand Down
21 changes: 14 additions & 7 deletions e2e/schematics/web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ describe('Web Components Applications', () => {
runCLI(`build ${appName}`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/styles.js`
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/styles-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles-es5.js`
);
runCLI(`build ${appName} --prod --output-hashing none`);
checkFilesExist(
`dist/apps/${appName}/index.html`,
`dist/apps/${appName}/polyfills.js`,
`dist/apps/${appName}/runtime.js`,
`dist/apps/${appName}/main.js`,
`dist/apps/${appName}/polyfills-es2015.js`,
`dist/apps/${appName}/runtime-es2015.js`,
`dist/apps/${appName}/main-es2015.js`,
`dist/apps/${appName}/polyfills-es5.js`,
`dist/apps/${appName}/runtime-es5.js`,
`dist/apps/${appName}/main-es5.js`,
`dist/apps/${appName}/styles.css`
);
const testResults = await runCLIAsync(`test ${appName}`);
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,7 @@
],
"testPathIgnorePatterns": [
"node_modules",
"<rootDir>/build/packages/angular/spec",
"web.*normalize\\.spec\\.js",
"web.*config\\.spec\\.js"
"<rootDir>/build/packages/angular/spec"
],
"collectCoverage": true,
"coverageReporters": [
Expand Down
1 change: 0 additions & 1 deletion packages/node/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as webpack from 'webpack';
import { Configuration, ProgressPlugin } from 'webpack';

import * as ts from 'typescript';
import { resolve } from 'path';

import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import CircularDependencyPlugin = require('circular-dependency-plugin');
Expand Down
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"license-webpack-plugin": "^1.4.0",
"source-map-support": "0.5.11",
"ts-loader": "5.3.1",
"tsconfig-paths-webpack-plugin": "3.2.0",
"webpack": "4.29.0",
"webpack-dev-server": "3.1.14",
"webpack-node-externals": "1.7.2"
Expand Down
55 changes: 31 additions & 24 deletions packages/web/src/builders/build/build.impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import { of } from 'rxjs';
import * as buildWebpack from '@angular-devkit/build-webpack';
jest.mock('tsconfig-paths-webpack-plugin');
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import * as webConfigUtils from '../../utils/web.config';
import { getMockContext, MockBuilderContext } from '../../utils/testing';
import { join } from 'path';
import * as fs from 'fs';
import * as indexHtmlUtils from '@angular-devkit/build-angular/src/angular-cli-files/utilities/index-file/write-index-html';

describe('WebBuildBuilder', () => {
let context: MockBuilderContext;
let testOptions: WebBuildBuilderOptions;
let runWebpack: jasmine.Spy;
let writeIndexHtml: jasmine.Spy;

beforeEach(async () => {
context = await getMockContext();
testOptions = {
index: 'apps/webapp/src/index.html',
differentialLoading: true,
budgets: [],
baseHref: '/',
deployUrl: '/',
Expand Down Expand Up @@ -48,7 +51,17 @@ describe('WebBuildBuilder', () => {
toString: () => JSON.stringify(stats)
});
return of({
success: true
success: true,
emittedFiles: [
{
file: 'scripts.js',
extension: '.js'
},
{
file: 'styles.css',
extension: '.css'
}
]
});
}
);
Expand All @@ -61,6 +74,12 @@ describe('WebBuildBuilder', () => {
}
}
});
spyOn(webConfigUtils, 'getWebConfig').and.returnValue({
config: 'config'
});
writeIndexHtml = spyOn(indexHtmlUtils, 'writeIndexHtml').and.returnValue(
of(null)
);
(<any>TsConfigPathsPlugin).mockImplementation(class MockPathsPlugin {});
});

Expand All @@ -77,30 +96,17 @@ describe('WebBuildBuilder', () => {
expect(buildEvent.success).toEqual(true);
});

describe('statsJson option', () => {
beforeEach(() => {
spyOn(fs, 'writeFileSync');
});
it('should write the HTML', async () => {
await run(testOptions, context).toPromise();

it('should generate a stats json', async () => {
await run(
{
...testOptions,
statsJson: true
},
context
).toPromise();
expect(writeIndexHtml).toHaveBeenCalled();
});

expect(fs.writeFileSync).toHaveBeenCalledWith(
join('/root/dist/apps/webapp/stats.json'),
JSON.stringify(
{
stats: 'stats'
},
null,
2
)
);
describe('differentialLoading', () => {
it('should call runWebpack twice', async () => {
await run(testOptions, context).toPromise();

expect(runWebpack).toHaveBeenCalledTimes(2);
});
});

Expand All @@ -115,6 +121,7 @@ describe('WebBuildBuilder', () => {
await run(
{
...testOptions,
differentialLoading: false,
webpackConfig: 'apps/webapp/webpack.config.js'
},
context
Expand Down
116 changes: 88 additions & 28 deletions packages/web/src/builders/build/build.impl.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import {
JsonObject,
normalize,
join as devkitJoin
} from '@angular-devkit/core';
import { runWebpack, BuildResult } from '@angular-devkit/build-webpack';

import { Observable, from } from 'rxjs';
import { writeFileSync } from 'fs';
import { resolve } from 'path';
import { Observable, from, of, forkJoin } from 'rxjs';
import { normalizeWebBuildOptions } from '../../utils/normalize';
import { getWebConfig } from '../../utils/web.config';
import { BuildBuilderOptions } from '../../utils/types';
import { concatMap, map } from 'rxjs/operators';
import {
bufferCount,
concatMap,
map,
mergeScan,
switchMap
} from 'rxjs/operators';
import { getSourceRoot } from '../../utils/source-root';
import { ScriptTarget } from 'typescript';
import { writeIndexHtml } from '@angular-devkit/build-angular/src/angular-cli-files/utilities/index-file/write-index-html';
import { NodeJsSyncHost } from '@angular-devkit/core/node';

export interface WebBuildBuilderOptions extends BuildBuilderOptions {
differentialLoading: boolean;
index: string;
budgets: any[];
baseHref: string;
Expand All @@ -36,40 +48,88 @@ export function run(
options: WebBuildBuilderOptions,
context: BuilderContext
): Observable<BuildResult> {
return from(getSourceRoot(context)).pipe(
const host = new NodeJsSyncHost();
return from(getSourceRoot(context, host)).pipe(
map(sourceRoot => {
options = normalizeWebBuildOptions(
options,
context.workspaceRoot,
sourceRoot
);
let config = getWebConfig(
context.workspaceRoot,
sourceRoot,
options,
context.logger
);
if (options.webpackConfig) {
config = require(options.webpackConfig)(config, {
let configs = [
getWebConfig(
context.workspaceRoot,
sourceRoot,
options,
configuration: context.target.configuration
});
context.logger,
options.differentialLoading ? ScriptTarget.ES2015 : null
)
];
if (options.differentialLoading) {
configs.push(
getWebConfig(
context.workspaceRoot,
sourceRoot,
options,
context.logger,
ScriptTarget.ES5
)
);
}
return config;
if (options.webpackConfig) {
configs = configs.map(config =>
require(options.webpackConfig)(config, {
options,
configuration: context.target.configuration
})
);
}
return configs;
}),
concatMap(config =>
runWebpack(config, context, {
concatMap(configs => {
const runWebpackOptions = {
logging: stats => {
if (options.statsJson) {
writeFileSync(
resolve(context.workspaceRoot, options.outputPath, 'stats.json'),
JSON.stringify(stats.toJson(), null, 2)
);
}

context.logger.info(stats.toString());
}
})
)
};
return forkJoin(
configs.map(config => runWebpack(config, context, runWebpackOptions))
).pipe(
switchMap(
([result1, result2 = { success: true, emittedFiles: [] }]) => {
const success = [result1, result2].every(result => result.success);

return (options.differentialLoading
? writeIndexHtml({
host,
outputPath: normalize(options.outputPath),
indexPath: devkitJoin(
normalize(context.workspaceRoot),
options.index
),
ES5BuildFiles: result2.emittedFiles,
ES2015BuildFiles: result1.emittedFiles,
baseHref: options.baseHref,
deployUrl: options.deployUrl,
scripts: options.scripts,
styles: options.styles
})
: of(null)
).pipe(
map(
() =>
({
success,
emittedFiles: [
...result1.emittedFiles,
...result2.emittedFiles
]
} as BuildResult)
)
);
}
)
);
})
);
}
5 changes: 5 additions & 0 deletions packages/web/src/builders/build/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
}
]
},
"differentialLoading": {
"type": "boolean",
"description": "Enable differential loading for es5 browsers",
"default": true
},
"extractCss": {
"type": "boolean",
"description": "Extract css into a .css file",
Expand Down
Loading

0 comments on commit 43af1cd

Please sign in to comment.