Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions aio/angular.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "yarn",
Expand All @@ -21,6 +21,7 @@
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"webWorkerTsConfig": "src/tsconfig.worker.json",
"aot": true,
"optimization": true,
"buildOptimizer": true,
Expand All @@ -35,7 +36,6 @@
"assets": [
"src/assets",
"src/generated",
"src/app/search/search-worker.js",
"src/pwa-manifest.json",
"src/google385281288605d160.html",
{
Expand Down Expand Up @@ -123,14 +123,14 @@
"karmaConfig": "src/karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"webWorkerTsConfig": "src/tsconfig.worker.json",
"scripts": [],
"styles": [
"src/styles.scss"
],
"assets": [
"src/assets",
"src/generated",
"src/app/search/search-worker.js",
"src/pwa-manifest.json",
"src/google385281288605d160.html",
{
Expand Down
2 changes: 0 additions & 2 deletions aio/ngsw-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
"files": [
"/index.html",
"/pwa-manifest.json",
"/app/search/search-worker.js",
"/assets/images/favicons/favicon.ico",
"/assets/js/*.js",
"/generated/lunr.min.js",
"/*.css",
"/*.js"
],
Expand Down
10 changes: 5 additions & 5 deletions aio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"e2e": "ng e2e --no-webdriver-update",
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
"setup": "yarn aio-use-npm && yarn example-use-npm",
"postsetup": "yarn ~~build-ie-polyfills && yarn ~~minify-lunr && yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
"postsetup": "yarn ~~build-ie-polyfills && yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
"presetup-local": "yarn presetup",
"setup-local": "yarn aio-use-local && yarn example-use-local",
"postsetup-local": "yarn postsetup",
Expand Down Expand Up @@ -69,8 +69,7 @@
"~~build": "ng build --configuration=stable",
"post~~build": "yarn build-404-page",
"~~build-ie-polyfills": "webpack-cli src/ie-polyfills.js -o src/generated/ie-polyfills.min.js --mode production",
"~~http-server": "http-server",
"~~minify-lunr": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/generated/lunr.min.js --source-map"
"~~http-server": "http-server"
},
"engines": {
"node": ">=10.9.0 <11.0.0",
Expand All @@ -89,6 +88,7 @@
"@angular/platform-browser-dynamic": "^7.0.0",
"@angular/router": "^7.0.0",
"@angular/service-worker": "^7.0.0",
"@types/lunr": "^2.3.2",
"@webcomponents/custom-elements": "^1.2.0",
"classlist.js": "^1.1.20150312",
"core-js": "^2.4.1",
Expand All @@ -97,8 +97,8 @@
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.12.1",
"@angular/cli": "7.2.1",
"@angular-devkit/build-angular": "0.800.0-beta.13",
"@angular/cli": "8.0.0-beta.13",
"@angular/compiler": "^7.0.0",
"@angular/compiler-cli": "^7.0.0",
"@angular/language-service": "^7.0.0",
Expand Down
2 changes: 1 addition & 1 deletion aio/scripts/_payload-limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime": 3713,
"main": 509261,
"main": 484710,
"polyfills": 53926
}
}
Expand Down
2 changes: 1 addition & 1 deletion aio/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class AppComponent implements OnInit {
// Do not initialize the search on browsers that lack web worker support
if ('Worker' in window) {
// Delay initialization by up to 2 seconds
this.searchService.initWorker('app/search/search-worker.js', 2000);
this.searchService.initWorker(2000);
}

this.onResize(window.innerWidth);
Expand Down
106 changes: 0 additions & 106 deletions aio/src/app/search/search-worker.js

This file was deleted.

104 changes: 104 additions & 0 deletions aio/src/app/search/search-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { WebWorkerMessage } from '../shared/web-worker-message';
import * as lunr from 'lunr';

const SEARCH_TERMS_URL = '/generated/docs/app/search-data.json';
let index: lunr.Index;
const pages: SearchInfo = {};

interface SearchInfo {
[key: string]: PageInfo;
}

interface PageInfo {
path: string;
type: string;
titleWords: string;
keyWords: string;
}

addEventListener('message', handleMessage);

// Create the lunr index - the docs should be an array of objects, each object containing
// the path and search terms for a page
function createIndex(loadIndex: IndexLoader): lunr.Index {
// The lunr typings are missing QueryLexer so we have to add them here manually.
const queryLexer = (lunr as any as { QueryLexer: { termSeparator: RegExp } }).QueryLexer;
queryLexer.termSeparator = lunr.tokenizer.separator = /\s+/;
return lunr(/** @this */function () {
this.ref('path');
this.field('titleWords', { boost: 10 });
this.field('headingWords', { boost: 5 });
this.field('members', { boost: 4 });
this.field('keywords', { boost: 2 });
loadIndex(this);
});
}

// The worker receives a message to load the index and to query the index
function handleMessage(message: { data: WebWorkerMessage }): void {
const type = message.data.type;
const id = message.data.id;
const payload = message.data.payload;
switch (type) {
case 'load-index':
makeRequest(SEARCH_TERMS_URL, function (searchInfo: PageInfo[]) {
index = createIndex(loadIndex(searchInfo));
postMessage({ type: type, id: id, payload: true });
});
break;
case 'query-index':
postMessage({ type: type, id: id, payload: { query: payload, results: queryIndex(payload) } });
break;
default:
postMessage({ type: type, id: id, payload: { error: 'invalid message type' } })
}
}

// Use XHR to make a request to the server
function makeRequest(url: string, callback: (response: any) => void): void {

// The JSON file that is loaded should be an array of PageInfo:
const searchDataRequest = new XMLHttpRequest();
searchDataRequest.onload = function () {
callback(JSON.parse(this.responseText));
};
searchDataRequest.open('GET', url);
searchDataRequest.send();
}


// Create the search index from the searchInfo which contains the information about each page to be indexed
function loadIndex(pagesData: PageInfo[]): IndexLoader {
return (indexBuilder: lunr.Builder) => {
// Store the pages data to be used in mapping query results back to pages
// Add search terms from each page to the search index
pagesData.forEach(page => {
indexBuilder.add(page);
pages[page.path] = page;
});
};
}

// Query the index and return the processed results
function queryIndex(query: string): PageInfo[] {
try {
if (query.length) {
let results = index.search(query);
if (results.length === 0) {
// Add a relaxed search in the title for the first word in the query
// E.g. if the search is "ngCont guide" then we search for "ngCont guide titleWords:ngCont*"
const titleQuery = 'titleWords:*' + query.split(' ', 1)[0] + '*';
results = index.search(query + ' ' + titleQuery);
}
// Map the hits into info about each page to be returned as results
return results.map(function (hit) { return pages[hit.ref]; });
}
} catch (e) {
// If the search query cannot be parsed the index throws an error
// Log it and recover
console.log(e);
}
return [];
}

type IndexLoader = (indexBuilder: lunr.Builder) => void;
6 changes: 3 additions & 3 deletions aio/src/app/search/search.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ describe('SearchService', () => {

describe('initWorker', () => {
it('should create the worker and load the index after the specified delay', fakeAsync(() => {
service.initWorker('some/url', 100);
service.initWorker(100);
expect(WebWorkerClient.create).not.toHaveBeenCalled();
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
tick(100);
expect(WebWorkerClient.create).toHaveBeenCalledWith('some/url', jasmine.any(NgZone));
expect(WebWorkerClient.create).toHaveBeenCalledWith(jasmine.any(Worker), jasmine.any(NgZone));
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
}));
});

describe('search', () => {
beforeEach(() => {
// We must initialize the service before calling connectSearches
service.initWorker('some/url', 1000);
service.initWorker(1000);
// Simulate the index being ready so that searches get sent to the worker
(service as any).ready = of(true);
});
Expand Down
6 changes: 3 additions & 3 deletions aio/src/app/search/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ export class SearchService {
* initial rendering of the web page. Triggering a search will override this delay and cause the index to be
* loaded immediately.
*
* @param workerUrl the url of the WebWorker script that runs the searches
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
*/
initWorker(workerUrl: string, initDelay: number) {
initWorker(initDelay: number) {
// Wait for the initDelay or the first search
const ready = this.ready = race<any>(
timer(initDelay),
Expand All @@ -28,7 +27,8 @@ export class SearchService {
.pipe(
concatMap(() => {
// Create the worker and load the index
this.worker = WebWorkerClient.create(workerUrl, this.zone);
const worker = new Worker('./search-worker', { type: 'module' });
this.worker = WebWorkerClient.create(worker, this.zone);
return this.worker.sendMessage<boolean>('load-index');
}),
publishReplay(1),
Expand Down
5 changes: 5 additions & 0 deletions aio/src/app/shared/web-worker-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface WebWorkerMessage {
type: string;
payload: any;
id?: number;
}
11 changes: 3 additions & 8 deletions aio/src/app/shared/web-worker.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import {NgZone} from '@angular/core';
import {Observable} from 'rxjs';

export interface WebWorkerMessage {
type: string;
payload: any;
id?: number;
}
import {WebWorkerMessage} from './web-worker-message';

export class WebWorkerClient {
private nextId = 0;

static create(workerUrl: string, zone: NgZone) {
return new WebWorkerClient(new Worker(workerUrl), zone);
static create(worker: Worker, zone: NgZone) {
return new WebWorkerClient(worker, zone);
}

private constructor(private worker: Worker, private zone: NgZone) {
Expand Down
Loading