摘要:在服務端渲染方面提供一套前后端同構解決方案,它就是統一平臺,一項在服務端運行應用的技術。這些著陸頁是純,并且即使被禁用了也能顯示。它會把客戶端請求轉換成服務端渲染的頁面。它是服務端渲染器和你的應用之間的橋梁。
Angular Universal
Angular在服務端渲染方面提供一套前后端同構解決方案,它就是 Angular Universal(統一平臺),一項在服務端運行 Angular 應用的技術。
標準的 Angular 應用會執行在瀏覽器中,它會在 DOM 中渲染頁面,以響應用戶的操作。
而 Angular Universal 會在服務端通過一個被稱為服務端渲染(server-side rendering - SSR)的過程生成靜態的應用頁面。
它可以生成這些頁面,并在瀏覽器請求時直接用它們給出響應。 它也可以把頁面預先生成為 HTML 文件,然后把它們作為靜態文件供服務端使用。
工作原理要制作一個 Universal 應用,就要安裝 platform-server 包。 platform-server 包提供了服務端的 DOM 實現、XMLHttpRequest 和其它底層特性,但不再依賴瀏覽器。
你要使用 platform-server 模塊而不是 platform-browser 模塊來編譯這個客戶端應用,并且在一個 Web 服務器上運行這個 Universal 應用。
服務器(下面的示例中使用的是 Node Express 服務器)會把客戶端對應用頁面的請求傳給 renderModuleFactory 函數。
renderModuleFactory 函數接受一個模板 HTML 頁面(通常是 index.html)、一個包含組件的 Angular 模塊和一個用于決定該顯示哪些組件的路由作為輸入。
該路由從客戶端的請求中傳給服務器。 每次請求都會給出所請求路由的一個適當的視圖。
renderModuleFactory 在模板中的
最后,服務器就會把渲染好的頁面返回給客戶端。
為什么要服務端渲染三個主要原因:
幫助網絡爬蟲(SEO)
提升在手機和低功耗設備上的性能
迅速顯示出第首頁
幫助網絡爬蟲(SEO)Google、Bing、百度、Facebook、Twitter 和其它搜索引擎或社交媒體網站都依賴網絡爬蟲去索引你的應用內容,并且讓它的內容可以通過網絡搜索到。
這些網絡爬蟲可能不會像人類那樣導航到你的具有高度交互性的 Angular 應用,并為其建立索引。
Angular Universal 可以為你生成應用的靜態版本,它易搜索、可鏈接,瀏覽時也不必借助 JavaScript。它也讓站點可以被預覽,因為每個 URL 返回的都是一個完全渲染好的頁面。
啟用網絡爬蟲通常被稱為搜索引擎優化 (SEO)。
提升手機和低功耗設備上的性能有些設備不支持 JavaScript 或 JavaScript 執行得很差,導致用戶體驗不可接受。 對于這些情況,你可能會需要該應用的服務端渲染、無 JavaScript 的版本。 雖然有一些限制,不過這個版本可能是那些完全沒辦法使用該應用的人的唯一選擇。
快速顯示首頁快速顯示首頁對于吸引用戶是至關重要的。
如果頁面加載超過了三秒中,那么 53% 的移動網站會被放棄。 你的應用需要啟動的更快一點,以便在用戶決定做別的事情之前吸引他們的注意力。
使用 Angular Universal,你可以為應用生成“著陸頁”,它們看起來就和完整的應用一樣。 這些著陸頁是純 HTML,并且即使 JavaScript 被禁用了也能顯示。 這些頁面不會處理瀏覽器事件,不過它們可以用 routerLink 在這個網站中導航。
在實踐中,你可能要使用一個著陸頁的靜態版本來保持用戶的注意力。 同時,你也會在幕后加載完整的 Angular 應用。 用戶會認為著陸頁幾乎是立即出現的,而當完整的應用加載完之后,又可以獲得完全的交互體驗。
示例解析下面將基于我在GitHub上的示例項目 angular-universal-starter 來進行講解。
這個項目與第一篇的示例項目一樣,都是基于 Angular CLI進行開發構建的,因此它們的區別只在于服務端渲染所需的那些配置上。
安裝工具在開始之前,下列包是必須安裝的(示例項目均已配置好,只需 npm install 即可):
@angular/platform-server - Universal 的服務端元件。
@nguniversal/module-map-ngfactory-loader - 用于處理服務端渲染環境下的惰性加載。
@nguniversal/express-engine - Universal 應用的 Express 引擎。
ts-loader - 用于對服務端應用進行轉譯。
express - Node Express 服務器
使用下列命令安裝它們:
npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine express項目配置
配置工作有:
創建服務端應用模塊:src/app/app.server.module.ts
修改客戶端應用模塊:src/app/app.module.ts
創建服務端應用的引導程序文件:src/main.server.ts
修改客戶端應用的引導程序文件:src/main.ts
創建 TypeScript 的服務端配置:src/tsconfig.server.json
修改 @angular/cli 的配置文件:.angular-cli.json
創建 Node Express 的服務程序:server.ts
創建服務端預渲染的程序:prerender.ts
創建 Webpack 的服務端配置:webpack.server.config.js
1、創建服務端應用模塊:src/app/app.server.module.tsimport { NgModule } from "@angular/core"; import { ServerModule, ServerTransferStateModule } from "@angular/platform-server"; import { ModuleMapLoaderModule } from "@nguniversal/module-map-ngfactory-loader"; import { AppBrowserModule } from "./app.module"; import { AppComponent } from "./app.component"; // 可以注冊那些在 Universal 環境下運行應用時特有的服務提供商 @NgModule({ imports: [ AppBrowserModule, // 客戶端應用的 AppModule ServerModule, // 服務端的 Angular 模塊 ModuleMapLoaderModule, // 用于實現服務端的路由的惰性加載 ServerTransferStateModule, // 在服務端導入,用于實現將狀態從服務器傳輸到客戶端 ], bootstrap: [AppComponent], }) export class AppServerModule { }
服務端應用模塊(習慣上叫作 AppServerModule)是一個 Angular 模塊,它包裝了應用的根模塊 AppModule,以便 Universal 可以在你的應用和服務器之間進行協調。 AppServerModule 還會告訴 Angular 再把你的應用以 Universal 方式運行時,該如何引導它。
2、修改客戶端應用模塊:src/app/app.module.tsimport { BrowserModule, BrowserTransferStateModule } from "@angular/platform-browser"; import { HttpClientModule } from "@angular/common/http"; import { APP_ID, Inject, NgModule, PLATFORM_ID } from "@angular/core"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { TransferHttpCacheModule } from "@nguniversal/common"; import { isPlatformBrowser } from "@angular/common"; import { AppRoutingModule } from "./app.routes"; @NgModule({ imports: [ AppRoutingModule, BrowserModule.withServerTransition({appId: "my-app"}), TransferHttpCacheModule, // 用于實現服務器到客戶端的請求傳輸緩存,防止客戶端重復請求服務端已完成的請求 BrowserTransferStateModule, // 在客戶端導入,用于實現將狀態從服務器傳輸到客戶端 HttpClientModule ], declarations: [ AppComponent, HomeComponent ], providers: [], bootstrap: [AppComponent] }) export class AppBrowserModule { constructor(@Inject(PLATFORM_ID) private platformId: Object, @Inject(APP_ID) private appId: string) { // 判斷運行環境為客戶端還是服務端 const platform = isPlatformBrowser(platformId) ? "in the browser" : "on the server"; console.log(`Running ${platform} with appId=${appId}`); } }
將 NgModule 的元數據中 BrowserModule 的導入改成 BrowserModule.withServerTransition({appId: "my-app"}),Angular 會把 appId 值(它可以是任何字符串)添加到服務端渲染頁面的樣式名中,以便它們在客戶端應用啟動時可以被找到并移除。
此時,我們可以通過依賴注入(@Inject(PLATFORM_ID) 及 @Inject(APP_ID))取得關于當前平臺和 appId 的運行時信息:
constructor(@Inject(PLATFORM_ID) private platformId: Object, @Inject(APP_ID) private appId: string) { // 判斷運行環境為客戶端還是服務端 const platform = isPlatformBrowser(platformId) ? "in the browser" : "on the server"; console.log(`Running ${platform} with appId=${appId}`); }3、創建服務端應用的引導程序文件:src/main.server.ts
該文件導出服務端模塊:
export { AppServerModule } from "./app/app.server.module";4、修改客戶端應用的引導程序文件:src/main.ts
監聽 DOMContentLoaded 事件,在發生 DOMContentLoaded 事件時運行我們的代碼,以使 TransferState 正常工作
import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { AppBrowserModule } from "./app/app.module"; import { environment } from "./environments/environment"; if (environment.production) { enableProdMode(); } // 在 DOMContentLoaded 時運行我們的代碼,以使 TransferState 正常工作 document.addEventListener("DOMContentLoaded", () => { platformBrowserDynamic().bootstrapModule(AppBrowserModule); });5、創建 TypeScript 的服務端配置:src/tsconfig.server.json
{ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "baseUrl": "./", "module": "commonjs", "types": [ "node" ] }, "exclude": [ "test.ts", "**/*.spec.ts" ], "angularCompilerOptions": { "entryModule": "app/app.server.module#AppServerModule" } }
與 tsconfig.app.json 的差異在于:
module 屬性必須是 commonjs,這樣它才能被 require() 方法導入你的服務端應用。
angularCompilerOptions 部分有一些面向 AOT 編譯器的選項:
entryModule - 服務端應用的根模塊,其格式為 path/to/file#ClassName。
6、修改 @angular/cli 的配置文件:.angular-cli.json在 apps 下添加:
{ "platform": "server", "root": "src", "outDir": "dist/server", "assets": [ "assets", "favicon.ico" ], "index": "index.html", "main": "main.server.ts", "test": "test.ts", "tsconfig": "tsconfig.server.json", "testTsconfig": "tsconfig.spec.json", "prefix": "", "styles": [ "styles.scss" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } }7、創建 Node Express 的服務程序:server.ts
import "zone.js/dist/zone-node"; import "reflect-metadata"; import { enableProdMode } from "@angular/core"; import * as express from "express"; import { join } from "path"; import { readFileSync } from "fs"; // Faster server renders w/ Prod mode (dev mode never needed) enableProdMode(); // Express server const app = express(); const PORT = process.env.PORT || 4000; const DIST_FOLDER = join(process.cwd(), "dist"); // Our index.html we"ll use as our template const template = readFileSync(join(DIST_FOLDER, "browser", "index.html")).toString(); // * NOTE :: leave this as require() since this file is built Dynamically from webpack const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require("./dist/server/main.bundle"); // Express Engine import { ngExpressEngine } from "@nguniversal/express-engine"; // Import module map for lazy loading import { provideModuleMap } from "@nguniversal/module-map-ngfactory-loader"; // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) app.engine("html", ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); app.set("view engine", "html"); app.set("views", join(DIST_FOLDER, "browser")); /* - Example Express Rest API endpoints - app.get("/api/**", (req, res) => { }); */ // Server static files from /browser app.get("*.*", express.static(join(DIST_FOLDER, "browser"), { maxAge: "1y" })); // ALl regular routes use the Universal engine app.get("*", (req, res) => { res.render("index", {req}); }); // Start up the Node server app.listen(PORT, () => { console.log(`Node Express server listening on http://localhost:${PORT}`); });
這個文件中最重要的部分是 ngExpressEngine 函數:
app.engine("html", ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] }));
ngExpressEngine 是對 Universal 的 renderModuleFactory 函數的封裝。它會把客戶端請求轉換成服務端渲染的 HTML 頁面。如果你使用不同于Node的服務端技術,你需要在該服務端的模板引擎中調用這個函數。
第一個參數是你以前寫過的 AppServerModule。 它是 Universal 服務端渲染器和你的應用之間的橋梁。
第二個參數是 extraProviders。它是在這個服務器上運行時才需要的一些可選的 Angular 依賴注入提供商。當你的應用需要那些只有當運行在服務器實例中才需要的信息時,就要提供 extraProviders 參數。
ngExpressEngine 函數返回了一個會解析成渲染好的頁面的承諾(Promise)。
接下來你的引擎要決定拿這個頁面做點什么。 現在這個引擎的回調函數中,把渲染好的頁面返回給了 Web 服務器,然后服務器通過 HTTP 響應把它轉發給了客戶端。
8、創建服務端預渲染的程序:prerender.ts// Load zone.js for the server. import "zone.js/dist/zone-node"; import "reflect-metadata"; import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; import { join } from "path"; import { enableProdMode } from "@angular/core"; // Faster server renders w/ Prod mode (dev mode never needed) enableProdMode(); // Import module map for lazy loading import { provideModuleMap } from "@nguniversal/module-map-ngfactory-loader"; import { renderModuleFactory } from "@angular/platform-server"; import { ROUTES } from "./static.paths"; // * NOTE :: leave this as require() since this file is built Dynamically from webpack const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require("./dist/server/main.bundle"); const BROWSER_FOLDER = join(process.cwd(), "browser"); // Load the index.html file containing referances to your application bundle. const index = readFileSync(join("browser", "index.html"), "utf8"); let previousRender = Promise.resolve(); // Iterate each route path ROUTES.forEach(route => { const fullPath = join(BROWSER_FOLDER, route); // Make sure the directory structure is there if (!existsSync(fullPath)) { mkdirSync(fullPath); } // Writes rendered HTML to index.html, replacing the file if it already exists. previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, { document: index, url: route, extraProviders: [ provideModuleMap(LAZY_MODULE_MAP) ] })).then(html => writeFileSync(join(fullPath, "index.html"), html)); });9、創建 Webpack 的服務端配置:webpack.server.config.js
Universal 應用不需要任何額外的 Webpack 配置,Angular CLI 會幫我們處理它們。但是由于本例子的 Node Express 的服務程序是 TypeScript 應用(server.ts及prerender.ts),所以要使用 Webpack 來轉譯它。這里不討論 Webpack 的配置,需要了解的移步 Webpack官網
// Work around for https://github.com/angular/angular-cli/issues/7200 const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { server: "./server.ts", // This is our Express server for Dynamic universal prerender: "./prerender.ts" // This is an example of Static prerendering (generative) }, target: "node", resolve: {extensions: [".ts", ".js"]}, externals: [/(node_modules|main..*.js)/,], // Make sure we include all node_modules etc output: { path: path.join(__dirname, "dist"), // Puts the output at the root of the dist folder filename: "[name].js" }, module: { rules: [ {test: /.ts$/, loader: "ts-loader"} ] }, plugins: [ new webpack.ContextReplacementPlugin( /(.+)?angular(|/)core(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression path.join(__dirname, "src"), // location of your src {} // a map of your routes ), new webpack.ContextReplacementPlugin( /(.+)?express(|/)(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression path.join(__dirname, "src"), {} ) ] };測試配置
通過上面的配置,我們就制作完成一個可在服務端渲染的 Angular Universal 應用。
在 package.json 的 scripts 區配置 build 和 serve 有關的命令:
{ "scripts": { "ng": "ng", "start": "ng serve -o", "ssr": "npm run build:ssr && npm run serve:ssr", "prerender": "npm run build:prerender && npm run serve:prerender", "build": "ng build", "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false", "build:prerender": "npm run build:client-and-server-bundles && npm run webpack:server && npm run generate:prerender", "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server", "generate:prerender": "cd dist && node prerender", "webpack:server": "webpack --config webpack.server.config.js --progress --colors", "serve:prerender": "cd dist/browser && http-server", "serve:ssr": "node dist/server" } }開發只需運行 npm run start 執行 npm run ssr 編譯應用程序,并啟動一個Node Express來為應用程序提供服務 http://localhost:4000
dist目錄:
執行npm run prerender - 編譯應用程序并預渲染應用程序文件,啟動一個演示http服務器,以便您可以查看它 http://localhost:8080注意: 要將靜態網站部署到靜態托管平臺,您必須部署dist/browser文件夾, 而不是dist文件夾
dist目錄:
根據項目實際的路由信息并在根目錄的 static.paths.ts 中配置,提供給 prerender.ts 解析使用。
export const ROUTES = [ "/", "/lazy" ];
因此,從dist目錄可以看到,服務端預渲染會根據配置好的路由在 browser 生成對應的靜態index.html。如 / 對應 /index.html,/lazy 對應 /lazy/index.html。
服務端的模塊懶加載在前面的介紹中,我們在 app.server.module.ts 中導入了 ModuleMapLoaderModule,在 app.module.ts。
ModuleMapLoaderModule 模塊可以使得懶加載的模塊也可以在服務端進行渲染,而你要做也只是在 app.server.module.ts 中導入。
服務端到客戶端的狀態傳輸在前面的介紹中,我們在 app.server.module.ts 中導入了 ServerTransferStateModule,在 app.module.ts 中導入了 BrowserTransferStateModule 和 TransferHttpCacheModule。
這三個模塊都與服務端到客戶端的狀態傳輸有關:
ServerTransferStateModule:在服務端導入,用于實現將狀態從服務端傳輸到客戶端
BrowserTransferStateModule:在客戶端導入,用于實現將狀態從服務端傳輸到客戶端
TransferHttpCacheModule:用于實現服務端到客戶端的請求傳輸緩存,防止客戶端重復請求服務端已完成的請求
使用這幾個模塊,可以解決 http請求在服務端和客戶端分別請求一次 的問題。
比如在 home.component.ts 中有如下代碼:
import { Component, OnDestroy, OnInit } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs/Observable"; @Component({ selector: "app-home", templateUrl: "./home.component.html", styleUrls: ["./home.component.scss"] }) export class HomeComponent implements OnInit, OnDestroy { constructor(public http: HttpClient) { } ngOnInit() { this.poiSearch(this.keyword, "北京市").subscribe((data: any) => { console.log(data); }); } ngOnDestroy() { } poiSearch(text: string, city?: string): Observable{ return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`)); } }
代碼運行之后,
服務端請求并打印:
客戶端再一次請求并打印:
方法1:使用 TransferHttpCacheModule使用 TransferHttpCacheModule 很簡單,代碼不需要改動。在 app.module.ts 中導入之后,Angular自動會將服務端請求緩存到客戶端,換句話說就是服務端請求到數據會自動傳輸到客戶端,客戶端接收到數據之后就不會再發送請求了。
方法2:使用 BrowserTransferStateModule該方法稍微復雜一些,需要改動一些代碼。
調整 home.component.ts 代碼如下:
import { Component, OnDestroy, OnInit } from "@angular/core"; import { makeStateKey, TransferState } from "@angular/platform-browser"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs/Observable"; const KFCLIST_KEY = makeStateKey("kfcList"); @Component({ selector: "app-home", templateUrl: "./home.component.html", styleUrls: ["./home.component.scss"] }) export class HomeComponent implements OnInit, OnDestroy { constructor(public http: HttpClient, private state: TransferState) { } ngOnInit() { // 采用一個標記來區分服務端是否已經拿到了數據,如果沒拿到數據就在客戶端請求,如果已經拿到數據就不發請求 const kfcList:any[] = this.state.get(KFCLIST_KEY, null as any); if (!this.kfcList) { this.poiSearch(this.keyword, "北京市").subscribe((data: any) => { console.log(data); this.state.set(KFCLIST_KEY, data as any); // 存儲數據 }); } } ngOnDestroy() { if (typeof window === "object") { this.state.set(KFCLIST_KEY, null as any); // 刪除數據 } } poiSearch(text: string, city?: string): Observable{ return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`)); } }
使用 const KFCLIST_KEY = makeStateKey("kfcList") 創建儲存傳輸數據的 StateKey
在 HomeComponent 的構造函數中注入 TransferState
在 ngOnInit 中根據 this.state.get(KFCLIST_KEY, null as any) 判斷數據是否存在(不管是服務端還是客戶端),存在就不再請求,不存在則請求數據并通過 this.state.set(KFCLIST_KEY, data as any) 存儲傳輸數據
在 ngOnDestroy 中根據當前是否客戶端來決定是否將存儲的數據進行刪除
客戶端與服務端渲染對比最后,我們分別通過這三個原因來進行對比:
幫助網絡爬蟲(SEO)
提升在手機和低功耗設備上的性能
迅速顯示出首頁
幫助網絡爬蟲(SEO)客戶端渲染:
服務端渲染:
從上面可以看到,服務端提前將信息渲染到返回的頁面上,這樣網絡爬蟲就能直接獲取到信息了(網絡爬蟲基本不會解析javascript的)。
提升在手機和低功耗設備上的性能這個原因通過上面就可以看出,對于一些低端的設備,直接顯示頁面總比要解析javascript性能高的多。
迅速顯示出首頁同樣在 Fast 3G 網絡條件下進行測試
客戶端渲染:
服務端渲染:
牢記幾件事情對于服務器軟件包,您可能需要將第三方模塊包含到nodeExternals白名單中
window, document, navigator 以及其它的瀏覽器類型 - 不存在于服務端 - 如果你直接使用,在服務端將無法正常工作。 以下幾種方法可以讓你的代碼正常工作:
可以通過PLATFORM_ID標記注入的Object來檢查當前平臺是瀏覽器還是服務器,然后使用瀏覽器端特有的類型
import { PLATFORM_ID } from "@angular/core"; import { isPlatformBrowser, isPlatformServer } from "@angular/common"; constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... } ngOnInit() { if (isPlatformBrowser(this.platformId)) { // 僅運行在瀏覽器端的代碼 ... } if (isPlatformServer(this.platformId)) { // 僅運行在服務端的代碼 ... } }
- 盡量**限制**或**避免**使用`setTimeout`。它會減慢服務器端的渲染過程。確保在組件的`ngOnDestroy`中刪除它們 - 對于RxJs超時,請確保在成功時 _取消_ 它們的流,因為它們也會降低渲染速度。
不要直接操作nativeElement,使用Renderer2,從而可以跨平臺改變應用視圖。
constructor(element: ElementRef, renderer: Renderer2) { this.renderer.setStyle(element.nativeElement, "font-size", "x-large"); }
解決應用程序在服務器上運行XHR請求,并在客戶端再次運行的問題
使用從服務器傳輸到客戶端的緩存(TransferState)
清楚了解與DOM相關的屬性和屬性之間的差異
盡量讓指令無狀態。對于有狀態指令,您可能需要提供一個屬性,以反映相應屬性的初始字符串值,例如img標簽中的url。對于我們的native元素,src屬性被反映為元素類型HTMLImageElement的src屬性
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93811.html
摘要:為了能夠支持跨平臺,通過抽象層封裝了不同平臺的差異。這樣我們就引出抽象類來對元素進行設置樣式屬性插入子元素等操作。 在《Angular開發實踐(六):服務端渲染》這篇文章的最后,我們也提到了在服務端渲染中需要牢記的幾件事件,其中就包括不要使用window、 document、 navigator等瀏覽器特有的類型以及直接操作DOM元素。 這樣就引出了 Angular 主要特性之一:橫跨...
摘要:已被所有主流瀏覽器支持在過去幾周蘋果的瀏覽器與微軟的瀏覽器分別發布新版本,支持了,再加上早已支持的和,已得到所有主流瀏覽器支持。 showImg(https://segmentfault.com/img/remote/1460000012086220?w=1240&h=823); 前端每周清單第 40 期: JS 的 Core 與 Cost,Node 內存溢出調試,Software 2...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
閱讀 3216·2021-11-23 09:51
閱讀 3558·2021-11-09 09:46
閱讀 3655·2021-11-09 09:45
閱讀 2938·2019-08-29 17:31
閱讀 1860·2019-08-26 13:39
閱讀 2715·2019-08-26 12:12
閱讀 3614·2019-08-26 12:08
閱讀 2235·2019-08-26 11:31