先介紹下小朋友 udao,首先是一個開源專案,程式碼足夠簡單,其次是跟隨 Angular 大小版本一起成長的專案,會定期更新所有依賴包以及相容最新版本的寫法
Github 地址也貼出來好多次了:github.com/OrangeXC/ud…
本來行文目的只是新框架推出,本著學習的目的自己搞個東西出來玩,文章只是記錄專案的開發更迭過程,以及遇到的坑。
udao 系列文章有
從 Angular 5 寫到 6,逐步擴充套件 PWA,SSR 等,今天有讀者提了一個 issue,跟著歷史文章一步一步學習發現文章寫的是 Angular 5,但是 github 專案已經升級到了 Angular 6,是不是要保留多個版本分支?
我的回答是否定的,Angular 版本更迭之快想必大家都瞭解,每次大小版本的更新我都會在 github 上修改程式碼,但是不會一直出更新文章,因為每次更新的可能就幾行程式碼,循序漸進的更迭,更希望讀者能多一分敏銳的嗅覺,與框架相關的的實戰類文章總有退出江湖的一天,取決於框架的升級更新和框架的衰亡,相信當今翻閱 jquery 的實戰文章的人寥寥無幾,Angular 目前正處於半年一大版的節奏,既然觀察到 Angular 6 推出了,準備學習 Angular 5 文章之前就應該先看下作者的專案連結是不是 Angular 5 的專案,況且專案只是參考,寫文章想引出更多的是踩坑的過程。
嘮叨了這麼多之後,正如標題 終章 udao 系列文章到本篇結束,以後每個版本會持續更新迭代到 github 上,升級的程式碼變化可以順著 git commit 記錄查到
升級依賴
首先升級 angular-cli 到最新版本的 6.0,升級之前記得先解除安裝清 cache
全域性
npm uninstall -g @angular/cli
npm cache verify
# if npm version is < 5 then use `npm cache clean`
npm install -g @angular/cli@latest
複製程式碼
本地
rm -rf node_modules dist # use rmdir /S/Q node_modules dist in Windows Command Prompt; use rm -r -fo node_modules,dist in Windows PowerShell
yarn add @angular/cli@latest
yarn
複製程式碼
執行 ng update --all
,從 angular-cli 1.7 開始支援 update,具體引數見github.com/angular/ang…
執行 --all
目的是修改 package.json,否則只提示不修改,親測這個 --all
引數有坑,會報各種異常,升級版本後還會重複提示升級,遇到警告可以採用降級方案,直接 ng update
,根據提示一個一個去 package.json 裡修改,再報錯就是和這個方法無緣了,採用遠古時期方案去 npm 官網一個個查出最新版本更新上去。
注:typescript 停留在 2.7.2,即可不要升級到 2.8+,yarn 會報警高,也就是 angular-cli 的無腦 bug,
ng update --all
建議升級到 2.8.3 不升級它就不往下跑,升級完 2.8.3 安裝 yarn 又警告被依賴的 typescript 版本應該 >2.7 & <2.8。
順利升級完所有依賴後,別忘了加幾個依賴上去
- yarn add @nguniversal/express-engine
- yarn add @nguniversal/module-map-ngfactory-loader
- yarn add @angular-devkit/build-angular -D
- yarn add webpack -D
- yarn add webpack-cli -D
服務端入口
本次升級服務端渲染藉助 @nguniversal 實現
首先將 server.ts
從 src 目錄移動到根路徑,並修改如下
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
// 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');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// 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 server listening on http://localhost:${PORT}`);
});
複製程式碼
此檔案需要 webpack 單獨打包,由於升級到了 webpack 4,原來的 webpack 3.x 語法需要稍作修改
webpack.config.js
更名為 webpack.server.config.js
,準確表達打包的目標
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: { server: './server.ts' },
resolve: { extensions: ['.js', '.ts'] },
target: 'node',
mode: 'none',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/node_modules/],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{ test: /\.ts$/, loader: 'ts-loader' }]
},
plugins: [
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580
// for 'WARNING Critical dependency: the request of a dependency is an expression'
new webpack.ContextReplacementPlugin(
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
};
複製程式碼
整個服務端入口完成了,下面搞一下服務端打包
服務端打包
服務端渲染專案,大家印象比較深刻的地方就是,客戶端和服務端分別打兩個 bundle,分別供瀏覽器和伺服器執行。
這裡也不例外
src 下面的 main.server.ts
指向了打包入口
export { AppServerModule } from './app/app.server.module';
複製程式碼
看下 src/app/app.server.module
裡面有哪些修改
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
...
@NgModule({
imports: [
...
ModuleMapLoaderModule
]
...
})
複製程式碼
增加了 ModuleMapLoaderModule
作用是使用模組對映代替原來的模組懶載入,加快 node 環境下的執行速度,整個 bundle 打包下來只有一個 js 檔案 6M 多
客戶端入口
客戶端部分和上一版 Angular 5 的專案差不多,這裡面優化了 module 的拆分,將 router 和 ui 部分抽離到單獨的檔案再引入,使得 app.module 檔案不那麼臃腫。
新增了 console 記錄頁面的渲染環境
export class AppModule {
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}`);
}
}
複製程式碼
配置檔案
程式碼層面的修改,上面介紹的差不多了,接下來是配置檔案,從命名到寫法都是 breaking change
首先根路徑下 .angular-cli.json
更名為 angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"udao": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {},
"serve": {},
"extract-i18n": {},
"test": {},
"lint": {},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
}
}
}
},
"udao-e2e": {}
},
"defaultProject": "udao"
}
複製程式碼
這個檔案具體怎麼從 angular 5 版本遷移過來的,因為沒有遷移文件說明,乾脆用最新的 cli 新建一個專案,把對應的值和入口替換,上面經過精簡的 json 關鍵是 architect 裡的 server,作用是指明服務端構建的工具,入口,出口,配置項。
一眼看上去與原來的配置檔案相比,多了一層 projects,也就是支援多專案構建。
PWA 升級
這也是 udao 進階 PWA 的點睛之筆,升級過程更是 angular-cli 本次升級的精華所在。
升級之前把原來所有與 PWA 配置相關的程式碼全部刪除,切忌保留任何相關程式碼,否則會帶來不必要的麻煩,事先最好先解除安裝已有的 @angular/pwa
包,清理完畢後只需要一行程式碼搞定 PWA
ng add @angular/pwa --project *project-name*
複製程式碼
沒錯,專案裡 PWA 相關的程式碼都填充到對應位置了,什麼都不用修改。
這個 PWA 和 SSR 本身有那麼一點衝突,怎麼講呢,兩者同樣是為了加快頁面首屏速度,
@angular/pwa
中的 service-worker 擴充套件預設會把 html 檔案快取到本地,這個 html 的內容部分是空的,每次訪問網頁時 service-worker 先進行請求攔截,把空內容頁面丟擲來,資料請求完全發生在前端,而我們希望的 SSR 是首屏請求在 node 端完成,直出完整 html,頁面也不會 loading 和白屏,但是不加 PWA 又不能離線和快取其它資源,好吧,這些細節上的問題可能沒那麼多人關心,當然有更好的解決方案歡迎交流。
語法變化
rxjs 升級到 6.x 引入方式和用法需要調整,專案太大不想調整的話 rxjs 提供了降級相容方案 rxjs-compat
,直接 npm 安裝即可。
更新指令碼
既然入口檔案和配置檔案都做了相應的修改,那 npm 的 script 命令也要跟著更新一波了
{
"scripts": {
"dev": "ng serve",
"start": "node dist/server.js",
"build:ssr": "run-s build:client-and-server-bundles webpack:server",
"build:client-and-server-bundles": "ng build --prod && ng run udao:server",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors",
},
}
複製程式碼
注:到這裡執行
npm run build:ssr
即可整體打包,不可將build:client-and-server-bundles
和webpack:server
調換位置,因為 server.ts 入口檔案中有對打包好的 server bundle 的引用require('./dist/server/main')
總結
到這裡 udao 小朋友成功的從 Angular 5 成功邁向了 Angular 6,也是本系列的最後一篇終章,總之 Angular 6 也有被歷史淘汰的一天,擁抱變化吧,喜歡玩 Angular 最新版本的歡迎關注一波 Github,這裡並沒有鼓吹大家 fork 和 star,感興趣就隨便看看,也沒達到讓大家作為範例的程度,整體來講版本的更新非常及時,功能的更新非常緩慢。