1# Accelerating Web Page Access 2 3When the web page loads slowly, you can use the capabilities of pre-connection, preloading, and prefetching POST requests to accelerate the access to the web page. 4For details about how to optimize the web page loading performance, see [Performance Optimization for Web Page Loading](https://developer.huawei.com/consumer/en/doc/best-practices/bpta-web-develop-optimization#section128761465256). 5 6## Preparsing and Preconnecting 7 8You can preparse or preconnect to the page to be loaded using [prepareForPageLoad()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prepareforpageload10), which is used for domain name-level optimization. This method only performs DNS resolution on URLs and establishes TCP connections, but does not obtain main resources and subresources. 9 10 In the following example, the page to be loaded is preconnected in the **onAppear** callback of the **Web** component. 11 12```ts 13// xxx.ets 14import { webview } from '@kit.ArkWeb'; 15 16@Entry 17@Component 18struct WebComponent { 19 webviewController: webview.WebviewController = new webview.WebviewController(); 20 21 build() { 22 Column() { 23 Button('loadData') 24 .onClick(() => { 25 if (this.webviewController.accessBackward()) { 26 this.webviewController.backward(); 27 } 28 }) 29 Web({ src: 'https://www.example.com/', controller: this.webviewController }) 30 .onAppear(() => { 31 // The second parameter specifies whether to preconnect to a URL. The value false means that only DNS resolution is conducted on the URL. 32 // The third parameter indicates the number of sockets to be preconnected. A maximum of six sockets are allowed. 33 webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2); 34 }) 35 } 36 } 37} 38``` 39 40You can also use [initializeWebEngine()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#initializewebengine) to initialize the web kernel in advance, and then call 41[prepareForPageLoad()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prepareforpageload10) after the kernel is initialized. This method is applicable to preparsing and preconnecting of the home page. 42 43 44 In the following example, the web kernel is initialized in advance and the home page is preconnected in **onCreate** of the UIAbility. 45 46```ts 47// xxx.ets 48import { webview } from '@kit.ArkWeb'; 49import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 50 51export default class EntryAbility extends UIAbility { 52 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 53 console.log("EntryAbility onCreate"); 54 webview.WebviewController.initializeWebEngine(); 55 // Replace 'https://www.example.com' with the actual URL to be accessed. 56 webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2); 57 AppStorage.setOrCreate("abilityWant", want); 58 console.log("EntryAbility onCreate done"); 59 } 60} 61``` 62 63## Prefetching 64 65This method is used for resource-level optimization. Based on predictions as to what page is to be loaded or visited, you can use [prefetchPage()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prefetchpage10) for prefetching. 66 67Prefetching downloads the resources required by the page in advance, including main resources and subresources, to avoid blocking page rendering. However, the JavaScript code of the web page is not executed. Before calling **prefetchPage()**, you must create a **WebviewController** instance bound to the **Web** component. 68 69In the following example, prefetching of a page is triggered in **onPageEnd**. 70 71```ts 72// xxx.ets 73import { webview } from '@kit.ArkWeb'; 74 75@Entry 76@Component 77struct WebComponent { 78 webviewController: webview.WebviewController = new webview.WebviewController(); 79 80 build() { 81 Column() { 82 Web({ src: 'https://www.example.com/', controller: this.webviewController }) 83 .onPageEnd(() => { 84 // Prefetch the page at https://www.iana.org/help/example-domains. 85 this.webviewController.prefetchPage('https://www.iana.org/help/example-domains'); 86 }) 87 } 88 } 89} 90``` 91 92## Prefetching a POST Request 93 94This method is used for request-level optimization. You can use [prefetchResource()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prefetchresource12) to prefetch a POST request on the page to be loaded. When the page loading is complete, you can use [clearPrefetchedResource()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#clearprefetchedresource12) to clear the cached prefetched resources that are no longer used. 95 96 The following is an example: In the **onAppear** event of the **Web** component, prefetch the POST request for the page that is about to be loaded; in the **onPageEnd** event, you can clear the cache of the prefetched POST request that is no longer needed. 97 98```ts 99// xxx.ets 100import { webview } from '@kit.ArkWeb'; 101 102@Entry 103@Component 104struct WebComponent { 105 webviewController: webview.WebviewController = new webview.WebviewController(); 106 107 build() { 108 Column() { 109 Web({ src: "https://www.example.com/", controller: this.webviewController }) 110 .onAppear(() => { 111 // Replace https://www.example1.com/post?e=f&g=h with the actual URL of the POST request to prefetch. 112 webview.WebviewController.prefetchResource( 113 { 114 url: "https://www.example1.com/post?e=f&g=h", 115 method: "POST", 116 formData: "a=x&b=y", 117 }, 118 [{ 119 headerKey: "c", 120 headerValue: "z", 121 },], 122 "KeyX", 500); 123 }) 124 .onPageEnd(() => { 125 // Clear the cache of prefetched resources that are no longer used. 126 webview.WebviewController.clearPrefetchedResource(["KeyX",]); 127 }) 128 } 129 } 130} 131``` 132 133If you can predict that a **Web** component is about to load a page or is about to navigate to a page that includes a POST request, you can use [prefetchResource()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prefetchresource12) to prefetch the POST request on the page to be loaded. 134 135 Here is an example of how you might initiate prefetching of a POST request for a page to visit, in the **onPageEnd** callback: 136 137```ts 138// xxx.ets 139import { webview } from '@kit.ArkWeb'; 140 141@Entry 142@Component 143struct WebComponent { 144 webviewController: webview.WebviewController = new webview.WebviewController(); 145 146 build() { 147 Column() { 148 Web({ src: 'https://www.example.com/', controller: this.webviewController }) 149 .onPageEnd(() => { 150 // Replace https://www.example1.com/post?e=f&g=h with the actual URL of the POST request to prefetch. 151 webview.WebviewController.prefetchResource( 152 { 153 url: "https://www.example1.com/post?e=f&g=h", 154 method: "POST", 155 formData: "a=x&b=y", 156 }, 157 [{ 158 headerKey: "c", 159 headerValue: "z", 160 },], 161 "KeyX", 500); 162 }) 163 } 164 } 165} 166``` 167 168You can also use [initializeWebEngine()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#initializewebengine) to initialize the kernel in advance, and then call [prefetchResource()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#prefetchresource12) to prefetch the POST request on the page to be loaded after the kernel is initialized. This approach is suitable for prefetching POST requests for the home page in advance. 169 170 In the following example, the web engine is initialized in advance and the POST request of the home page is preobtained in **onCreate()** of the ability. 171 172```ts 173// xxx.ets 174import { webview } from '@kit.ArkWeb'; 175import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 176 177export default class EntryAbility extends UIAbility { 178 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 179 console.log("EntryAbility onCreate"); 180 webview.WebviewController.initializeWebEngine(); 181 // Replace https://www.example1.com/post?e=f&g=h with the actual URL of the POST request to prefetch. 182 webview.WebviewController.prefetchResource( 183 { 184 url: "https://www.example1.com/post?e=f&g=h", 185 method: "POST", 186 formData: "a=x&b=y", 187 }, 188 [{ 189 headerKey: "c", 190 headerValue: "z", 191 },], 192 "KeyX", 500); 193 AppStorage.setOrCreate("abilityWant", want); 194 console.log("EntryAbility onCreate done"); 195 } 196} 197``` 198 199## Precompiling for the Compilation Cache 200 201You can use [precompileJavaScript()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#precompilejavascript12) to generate the compilation cache of the script file before page loading. 202 203You are advised to use this function together with dynamic components, use offline **Web** components to generate bytecode caches, and load the service **Web** component at the appropriate time to use the bytecode caches. The example code is as follows: 204 2051. Save **UIContext** to **localStorage** in **EntryAbility**. 206 207 ```ts 208 // EntryAbility.ets 209 import { UIAbility } from '@kit.AbilityKit'; 210 import { window } from '@kit.ArkUI'; 211 212 const localStorage: LocalStorage = new LocalStorage('uiContext'); 213 214 export default class EntryAbility extends UIAbility { 215 storage: LocalStorage = localStorage; 216 217 onWindowStageCreate(windowStage: window.WindowStage) { 218 windowStage.loadContent('pages/Index', this.storage, (err, data) => { 219 if (err.code) { 220 return; 221 } 222 223 this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext()); 224 }); 225 } 226 } 227 ``` 228 2292. Compile the basic code of the dynamic component. 230 231 ```ts 232 // DynamicComponent.ets 233 import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 234 235 export interface BuilderData { 236 url: string; 237 controller: WebviewController; 238 context: UIContext; 239 } 240 241 let storage : LocalStorage | undefined = undefined; 242 243 export class NodeControllerImpl extends NodeController { 244 private rootNode: BuilderNode<BuilderData[]> | null = null; 245 private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null; 246 247 constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>, context: UIContext) { 248 storage = context.getSharedLocalStorage(); 249 super(); 250 this.wrappedBuilder = wrappedBuilder; 251 } 252 253 makeNode(): FrameNode | null { 254 if (this.rootNode != null) { 255 return this.rootNode.getFrameNode(); 256 } 257 return null; 258 } 259 260 initWeb(url: string, controller: WebviewController) { 261 if(this.rootNode != null) { 262 return; 263 } 264 265 const uiContext: UIContext = storage!.get<UIContext>("uiContext") as UIContext; 266 if (!uiContext) { 267 return; 268 } 269 this.rootNode = new BuilderNode(uiContext); 270 this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller }); 271 } 272 } 273 274 export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => { 275 const baseNode = new NodeControllerImpl(wrappedBuilder, data.context); 276 baseNode.initWeb(data.url, data.controller); 277 return baseNode; 278 } 279 ``` 280 2813. Compile the component for generating the bytecode cache. In this example, the local Javascript resource reads the local file in the **rawfile** directory through **readRawFile()**. 282 283 ```ts 284 // PrecompileWebview.ets 285 import { BuilderData } from "./DynamicComponent"; 286 import { Config, configs } from "./PrecompileConfig"; 287 288 @Builder 289 function WebBuilder(data: BuilderData) { 290 Web({ src: data.url, controller: data.controller }) 291 .onControllerAttached(() => { 292 precompile(data.controller, configs, data.context); 293 }) 294 .fileAccess(true) 295 } 296 297 export const precompileWebview = wrapBuilder<BuilderData[]>(WebBuilder); 298 299 export const precompile = async (controller: WebviewController, configs: Array<Config>, context: UIContext) => { 300 for (const config of configs) { 301 let content = await readRawFile(config.localPath, context); 302 303 try { 304 controller.precompileJavaScript(config.url, content, config.options) 305 .then(errCode => { 306 console.error("precompile successfully! " + errCode); 307 }).catch((errCode: number) => { 308 console.error("precompile failed. " + errCode); 309 }); 310 } catch (err) { 311 console.error("precompile failed. " + err.code + " " + err.message); 312 } 313 } 314 } 315 316 async function readRawFile(path: string, context: UIContext) { 317 try { 318 return await context.getHostContext()!.resourceManager.getRawFileContent(path);; 319 } catch (err) { 320 return new Uint8Array(0); 321 } 322 } 323 ``` 324 325JavaScript resources can also be obtained through [Data Request](../reference/apis-network-kit/js-apis-http.md). However, the format of HTTP response header obtained using this method is not standard. Additional steps are required to convert the response header into the standard HTTP response header format before use. If the response header obtained through a network request is **e-tag**, convert it to **E-Tag** before using it. 326 3274. Compile the code of the service component. 328 329 ```ts 330 // BusinessWebview.ets 331 import { BuilderData } from "./DynamicComponent"; 332 333 @Builder 334 function WebBuilder(data: BuilderData) { 335 // The component can be extended as required. 336 Web({ src: data.url, controller: data.controller }) 337 .cacheMode(CacheMode.Default) 338 } 339 340 export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder); 341 ``` 342 3435. Edit the resource configuration information. 344 345 ```ts 346 // PrecompileConfig.ets 347 import { webview } from '@kit.ArkWeb' 348 349 export interface Config { 350 url: string, 351 localPath: string, // Local resource path. 352 options: webview.CacheOptions 353 } 354 355 export let configs: Array<Config> = [ 356 { 357 url: "https://www.example.com/example.js", 358 localPath: "example.js", 359 options: { 360 responseHeaders: [ 361 { headerKey: "E-Tag", headerValue: "aWO42N9P9dG/5xqYQCxsx+vDOoU="}, 362 { headerKey: "Last-Modified", headerValue: "Wed, 21 Mar 2024 10:38:41 GMT"} 363 ] 364 } 365 } 366 ] 367 ``` 368 3696. Use the components on the page. 370 371 ```ts 372 // Index.ets 373 import { webview } from '@kit.ArkWeb'; 374 import { NodeController } from '@kit.ArkUI'; 375 import { createNode } from "./DynamicComponent" 376 import { precompileWebview } from "./PrecompileWebview" 377 import { businessWebview } from "./BusinessWebview" 378 379 @Entry 380 @Component 381 struct Index { 382 @State precompileNode: NodeController | undefined = undefined; 383 precompileController: webview.WebviewController = new webview.WebviewController(); 384 385 @State businessNode: NodeController | undefined = undefined; 386 businessController: webview.WebviewController = new webview.WebviewController(); 387 388 aboutToAppear(): void { 389 // Initialize the Web component used to inject local resources. 390 this.precompileNode = createNode(precompileWebview, 391 { url: "https://www.example.com/empty.html", controller: this.precompileController, context: this.getUIContext()}); 392 } 393 394 build() { 395 Column() { 396 // Load the service Web component at a proper time. In this example, the Web component is used in a button onclick event. 397 Button ("Loading page") 398 .onClick(() => { 399 this.businessNode = createNode(businessWebview, { 400 url: "https://www.example.com/business.html", 401 controller: this.businessController, 402 context: this.getUIContext() 403 }); 404 }) 405 // The Web component used for the service. 406 NodeContainer(this.businessNode); 407 } 408 } 409 } 410 ``` 411 412If you want to update the local generated compiled bytecode, edit the value of **E-Tag** or **Last-Modified** in the **responseHeaders** parameter of **cacheOptions**, and call the API again. 413 414## Injecting Offline Resources Without Interception 415You can use [injectOfflineResources()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#injectofflineresources12) to inject images, style sheets, or script resources to the memory cache of applications before page loading. 416 417You are advised to use this function together with dynamic components, use offline **Web** components to inject resources into the memory cache of the kernel, and load the service **Web** component at the appropriate time to use these resources. The example code is as follows: 418 4191. Save **UIContext** to **localStorage** in **EntryAbility**. 420 421 ```ts 422 // EntryAbility.ets 423 import { UIAbility } from '@kit.AbilityKit'; 424 import { window } from '@kit.ArkUI'; 425 426 const localStorage: LocalStorage = new LocalStorage('uiContext'); 427 428 export default class EntryAbility extends UIAbility { 429 storage: LocalStorage = localStorage; 430 431 onWindowStageCreate(windowStage: window.WindowStage) { 432 windowStage.loadContent('pages/Index', this.storage, (err, data) => { 433 if (err.code) { 434 return; 435 } 436 437 this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext()); 438 }); 439 } 440 } 441 ``` 442 4432. Compile the basic code of the dynamic component. 444 445 ```ts 446 // DynamicComponent.ets 447 import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 448 449 export interface BuilderData { 450 url: string; 451 controller: WebviewController; 452 context: UIContext; 453 } 454 455 let storage : LocalStorage | undefined = undefined; 456 457 export class NodeControllerImpl extends NodeController { 458 private rootNode: BuilderNode<BuilderData[]> | null = null; 459 private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null; 460 461 constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>, context: UIContext) { 462 storage = context.getSharedLocalStorage(); 463 super(); 464 this.wrappedBuilder = wrappedBuilder; 465 } 466 467 makeNode(): FrameNode | null { 468 if (this.rootNode != null) { 469 return this.rootNode.getFrameNode(); 470 } 471 return null; 472 } 473 474 initWeb(url: string, controller: WebviewController) { 475 if(this.rootNode != null) { 476 return; 477 } 478 479 const uiContext: UIContext = storage!.get<UIContext>("uiContext") as UIContext; 480 if (!uiContext) { 481 return; 482 } 483 this.rootNode = new BuilderNode(uiContext); 484 this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller }); 485 } 486 } 487 488 export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => { 489 const baseNode = new NodeControllerImpl(wrappedBuilder, data.context); 490 baseNode.initWeb(data.url, data.controller); 491 return baseNode; 492 } 493 ``` 494 4953. Compile the component code for injecting resources. In this example, the local resource reads the local file in the **rawfile** directory through **readRawFile()**. 496 497 <!--code_no_check--> 498 ```ts 499 // InjectWebview.ets 500 import { webview } from '@kit.ArkWeb'; 501 import { resourceConfigs } from "./Resource"; 502 import { BuilderData } from "./DynamicComponent"; 503 504 @Builder 505 function WebBuilder(data: BuilderData) { 506 Web({ src: data.url, controller: data.controller }) 507 .onControllerAttached(async () => { 508 try { 509 data.controller.injectOfflineResources(await getData (data.context)); 510 } catch (err) { 511 console.error("error: " + err.code + " " + err.message); 512 } 513 }) 514 .fileAccess(true) 515 } 516 517 export const injectWebview = wrapBuilder<BuilderData[]>(WebBuilder); 518 519 export async function getData(context: UIContext) { 520 const resourceMapArr: Array<webview.OfflineResourceMap> = []; 521 522 // Read the configuration, and read the file content from the rawfile directory. 523 for (let config of resourceConfigs) { 524 let buf: Uint8Array = new Uint8Array(0); 525 if (config.localPath) { 526 buf = await readRawFile(config.localPath, context); 527 } 528 529 resourceMapArr.push({ 530 urlList: config.urlList, 531 resource: buf, 532 responseHeaders: config.responseHeaders, 533 type: config.type, 534 }) 535 } 536 537 return resourceMapArr; 538 } 539 540 export async function readRawFile(url: string, context: UIContext) { 541 try { 542 return await context.getHostContext()!.resourceManager.getRawFileContent(url); 543 } catch (err) { 544 return new Uint8Array(0); 545 } 546 } 547 ``` 548 5494. Compile the code of the service component. 550 551 <!--code_no_check--> 552 ```ts 553 // BusinessWebview.ets 554 import { BuilderData } from "./DynamicComponent"; 555 556 @Builder 557 function WebBuilder(data: BuilderData) { 558 // The component can be extended as required. 559 Web({ src: data.url, controller: data.controller }) 560 .cacheMode(CacheMode.Default) 561 } 562 563 export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder); 564 ``` 565 5665. Edit the resource configuration information. 567 568 ```ts 569 // Resource.ets 570 import { webview } from '@kit.ArkWeb'; 571 572 export interface ResourceConfig { 573 urlList: Array<string>, 574 type: webview.OfflineResourceType, 575 responseHeaders: Array<Header>, 576 localPath: string, // The path for storing local resources in the rawfile directory. 577 } 578 579 export const resourceConfigs: Array<ResourceConfig> = [ 580 { 581 localPath: "example.png", 582 urlList: [ 583 "https://www.example.com/", 584 "https://www.example.com/path1/example.png", 585 "https://www.example.com/path2/example.png", 586 ], 587 type: webview.OfflineResourceType.IMAGE, 588 responseHeaders: [ 589 { headerKey: "Cache-Control", headerValue: "max-age=1000" }, 590 { headerKey: "Content-Type", headerValue: "image/png" }, 591 ] 592 }, 593 { 594 localPath: "example.js", 595 urlList: [ // Only one URL is provided. This URL is used as both the resource origin and the network request address of the resource. 596 "https://www.example.com/example.js", 597 ], 598 type: webview.OfflineResourceType.CLASSIC_JS, 599 responseHeaders: [ 600 // Used in <script crossorigin="anonymous"/> mode to provide additional response headers. 601 { headerKey: "Cross-Origin", headerValue:"anonymous" } 602 ] 603 }, 604 ]; 605 ``` 606 6076. Use the components on the page. 608 ```ts 609 // Index.ets 610 import { webview } from '@kit.ArkWeb'; 611 import { NodeController } from '@kit.ArkUI'; 612 import { createNode } from "./DynamicComponent" 613 import { injectWebview } from "./InjectWebview" 614 import { businessWebview } from "./BusinessWebview" 615 616 @Entry 617 @Component 618 struct Index { 619 @State injectNode: NodeController | undefined = undefined; 620 injectController: webview.WebviewController = new webview.WebviewController(); 621 622 @State businessNode: NodeController | undefined = undefined; 623 businessController: webview.WebviewController = new webview.WebviewController(); 624 625 aboutToAppear(): void { 626 // Initialize the Web component used to inject local resources and provide an empty HTML page as the URL. 627 this.injectNode = createNode(injectWebview, 628 { url: "https://www.example.com/empty.html", controller: this.injectController, context: this.getUIContext()}); 629 } 630 631 build() { 632 Column() { 633 // Load the service Web component at a proper time. In this example, the Web component is used in a button onclick event. 634 Button ("Loading page") 635 .onClick(() => { 636 this.businessNode = createNode(businessWebview, { 637 url: "https://www.example.com/business.html", 638 controller: this.businessController, 639 context: this.getUIContext() 640 }); 641 }) 642 // The Web component used for the service. 643 NodeContainer(this.businessNode); 644 } 645 } 646 } 647 ``` 648 6497. Example of a loaded HTML web page: 650 651 ```HTML 652 <!DOCTYPE html> 653 <html lang="en"> 654 <head></head> 655 <body> 656 <img src="https://www.example.com/path1/request.png" /> 657 <img src="https://www.example.com/path2/request.png" /> 658 <script src="https://www.example.com/example.js" crossorigin="anonymous"></script> 659 </body> 660 </html> 661 ``` 662