github鍦板潃椤圭洰URL-pika-musicpika-musicapi鏈嶅姟鍣ㄥ弬鑰傿inaryify鐨凬eteaseCloudMusicApi椤圭洰鎶€鏈壒鎬WA鏀寔銆傛敮鎸丳WA鐨勬祻瑙堝櫒鍙互鍦ㄦ闈㈠畨瑁匯eact-SSR妗嗘灦瀹炵幇DynamicImport缁撳悎SSR瀹炵幇webpack鎵撳寘鏀寔module/nomudule妯″紡瀹炵幇鍏ㄧ珯鍥剧墖鎳掑姞杞藉叾浠栫壒鎬э細http2Android鏀寔lock灞忓箷闊充箰鎺у埗妯箙杞挱缁勪欢瑙嗛鍜岄煶棰戞挱鏀剧粍浠剁綉绔欐埅鍥炬妧鏈壒鎬т粙缁峈eact-SSR妗嗘灦浠嬬粛涓昏鎬濇兂鍙傝€僋extJS銆傞灞忔湇鍔″櫒娓叉煋鏃讹紝璋冪敤缁勪欢鐨刧etInitialProps(store)鏂规硶锛屾敞鍏eduxstore锛実etInitialProps鑾峰彇椤甸潰鐨勬暟鎹紝骞跺皢鏁版嵁瀛樺偍鍒皉eduxstore涓€傚鎴风hydrate鏃讹紝浠巖eduxstore涓幏鍙栨暟鎹紝鐒跺悗灏嗘暟鎹敞鍏ュ埌swr鐨刬nitialData涓紝鍚庣画椤甸潰鐨勬暟鎹幏鍙栧拰鏇存柊浣跨敤swr鐨勮兘鍔涖€傞潪SSR椤甸潰灏嗙洿鎺ヤ娇鐢╯wr銆傛垜浠互棣栭〉锛圖iscover锛変负渚嬶細椤圭洰涓殑鐖剁被ConnectCompReducer锛歝hildmustimplememntthismethod!")}}姣忎釜瀹炵幇SSR鐨勯〉闈㈤兘闇€瑕佺户鎵胯繖涓被锛屾瘮濡備富椤甸潰锛歝lassConnectDiscoverReducerextendsConnectCompReducer{//Discover椤甸潰浼氬疄鐜扮殑getInitialProps鏂规硶灏辨槸璋冪敤getInitialData娉ㄥ叆reduxstoregetInitialData=asyncstore=>{}}exportdefaultnewConnectDiscoverReducer()Discover鐨凧SX:importdiscoverPagefrom"./connectDiscoverReducer"constDiscover=memo(()=>{//妯箙鏁版嵁constinitialBannerList=useSelector(state=>state.discover.bannerList)//灏嗘í骞呮暟鎹敞鍏wr鐨刬nitialDataconst{data:bannerList}=useSWR("/api/banner?type=2",discoverPage.requestBannerList,{initialData:initialBannerList,},)return(......)})Discover.getInitialProps=async(store,ctx)=>{//store->reduxstore,ctx->koa'sctxawaitdiscoverPage.getInitialData(store,ctx)}鏈嶅姟鍣ㄦ暟鎹殑鑾峰彇://matchedRoutes:鍖归厤鐨勮矾鐢遍〉闈㈤渶瑕佺粨鍚堝姩鎬佸鍏ャ€備笅涓€鑺備細浠嬬粛constsetInitialDataToStore=async(matchedRoutes,ctx)=>{//鑾峰彇reduxstoreconststore=getReduxStore({config:{ua:ctx.state.ua,},})//600ms鍚庤秴鏃讹紝涓柇鏁版嵁鑾峰彇awaitPromise.race([Promise.allSettled(matchedRoutes.map(item=>{returnPromise.resolve(//璋冪敤椤甸潰鐨刧etInitialProps鏂规硶item.route?.component?.getInitialProps?.(store,ctx)??null,)}),),newPromise(resolve=>setTimeout(()=>resolve(),600)),]).catch(error=>{console.error("renderHTML41,",error)})returnstore}缁撳悎SSR鐨勫姩鎬佸鍏ラ〉闈㈣嚜琛屽疄鐜板姩鎬佸鍏ョ殑灏佽銆傞噸瑕佺殑澶勭悊鏄姞杞介敊璇悗閲嶈瘯锛岄伩鍏嶉〉闈㈠姞杞介棯閫€锛歝lassLoadableextendsReact銆傜粍浠秢鏋勯€燾tor(props){super(props)this.state={Comp:null,error:null,isTimeout:false,}}//eslint-disable-next-linereact/sort-compraceLoading=()=>{const{pastDelay}=this.propsreturnnewPromise((_,reject)=>{setTimeout(()=>reject(newError("timeout")),pastDelay||200)})}load=async()=>{const{loader}=this.propstry{this.setState({error:null,})//raceLoading閬垮厤椤甸潰鍔犺浇闂幇constloadedComp=awaitPromise.race([this.raceLoading(),loader()])this.setState({isTimeout:false,Comp:loadedComp&&loadedComp.__esModule?loadedComp.default:loadedComp,})}catch(e){if(e.message==="瓒呮椂"){this.setState({isTimeout:true,})this.load()}else{this.setState({error:e,})}}}componentDidMount(){this.load()}render(){const{error,isTimeout,Comp}=this.stateconst{loading}=this.props//鍔犺浇閿欒锛岄噸璇昳f(error)returnloading({error,retry:this.load})if(isTimeout)returnloading({pastDelay:true})if(Comp)returnreturnnull}}鏍囪鍔ㄦ€佸姞杞界粍浠剁敤浜庢湇鍔$璇嗗埆锛歝onstasyncLoader=({loader,loading,pastDelay})=>{constimportable=props=>()//鏍囪鍙鍏ャ€俰sAsyncComp=truereturnimportable}灏佽椤甸潰鍔ㄦ€佸姞杞藉悗锛岄渶瑕佽€冭檻涓ょ偣锛氫娇鐢╯sr鏃讹紝闇€瑕佷富鍔ㄦ墽琛屽姩鎬佽矾鐢辩粍浠讹紝鍚﹀垯鏈嶅姟鍣ㄤ笉浼氭覆鏌撶粍浠舵湰韬殑鍐呭锛屼互鍙婃祻瑙堝櫒涓嶄細鍏堝姞杞藉鏋滅粍浠舵槸鍔ㄦ€佹媶鍒嗙殑锛岀粍浠剁殑鍔犺浇鐘舵€佷細闂儊銆傛墍浠ュ繀椤诲厛鍔犺浇鍔ㄦ€佽矾鐢辩粍浠讹紝鐒跺悗鍐嶆覆鏌撻〉闈€傚叿浣撲唬鐮佸涓嬶細鏈嶅姟绔姞杞芥爣璁颁负isAsyncComp鐨勫姩鎬佺粍浠讹細if(route.routes){return{...route,routes:awaitPromise.allSettled([...route.routes].map(asynccompRoute=>{const{component}=compRouteif(component.isAsyncComp){灏濊瘯{constRealComp=awaitcomponent().props.loader()constReactComp=RealComp&&RealComp.__esModule?RealComp.default:RealCompreturn{...compRoute,component:ReactComp,}}catch(e){console.error(e)}}杩斿洖compRoute}),).then(res=>res.map(r=>r.value)),}}return{...route,}}),).then(res=>res.map(r=>r.value))returnssrRoutes}娴忚鍣ㄥ姞杞藉姩鎬佺粍浠讹細constclientPreloadReady=asyncroutes=>{try{//鍖归厤褰撳墠椤甸潰鐨勭粍浠禼onstmatchedRoutes=matchRoutes(routes,window.location.pathname)if(matchedRoutes&&matchedRoutes.length){awaitPromise.allSettled(matchedRoutes.map(asyncroute=>{if(route?.route?.component?.isAsyncComp&&!route?.route?.component.csr){try{awaitroute.route.component().props.loader()}catch(e){awaitPromise.reject(e)}}}),)}}catch(e){console.error(e)}}鏈€鍚庡湪娴忚鍣ㄧ鍔犺浇reactDOM.hydrate鏃跺厛鍔ㄦ€佹媶鍒哋utgoing缁勪欢:clientPreloadReady(routes).then(()=>{render(,document.getElementById("root"))})module/nomudule妯″紡涓昏瀹炵幇鎬濊矾锛歸ebpack鍏堟牴鎹畐ebpack.client.js鐨勯厤缃墦鍖呮敮鎸乪s妯″潡鐨勪唬鐮侊紝鐢熸垚index.html锛岀劧鍚巜ebpack鏍规嵁閰嶇疆浣跨敤涓婁竴姝ョ殑index.html浣滀负妯℃澘webpack.client.lengacy.js锛屽皢涓嶆敮鎸乪s妯″潡鐨勪唬鐮佹墦鍖咃紝鎻掑叆scriptnomodule鍜宻cripttype="module"銆備富瑕佷緷璧杊tmlwebpackplugin鐨勭浉鍏砲ooks銆倃ebpack.client.js鍜寃ebpack.client.lengacy.js鐨勪富瑕佸尯鍒湪浜巄abel閰嶇疆鍜宧tmlwebpack鎻掍欢鐨則emplatebabelpresets閰嶇疆锛歟xports.babelPresets=env=>{constcommon=["@babel/preset-env",{//targets:{esmodules:true},useBuiltIns:"usage",modules:false,debug:false,bugfixes:true,corejs:{version:3,proposals:true},},]if(env==="node"){common[1].targets={node:"13",}}elseif(env==="legacy"){common[1].targets={ios:"9",safari:"9",}common[1].bugfixes=false}else{common[1].targets={esmodules:true,}}returncommon}瀹炵幇鎻掑叆scriptnomodule鍜宻cripttype="鐨剋ebpack鎻掍欢浠g爜module"inhtml閾炬帴锛歨ttps://github.com/mbaxszy7/p...鍏ㄧ珯鍥剧墖鎳掑姞杞藉浘鐗囨噿鍔犺浇鐨勫疄鐜颁娇鐢↖ntersectionObserver鍜屾祻瑙堝櫒鍘熺敓鏀寔鐨勫浘鐗囨噿鍔犺浇pikaLazy=options=>{//濡傛灉娴忚鍣ㄥ師鐢熸敮鎸佸浘鐗囧欢杩熷姞杞斤紝鍒欒缃綋鍓嶅浘鐗囧欢杩熷姞杞絠f("loading"inHTMLImageElement.prototype){return{lazyObserver:imgRef=>{load(imgRef)},}}//褰撳綋鍓嶅浘鍍忓嚭鐜板湪褰撳墠瑙嗗彛鏃讹紝鍔犺浇鍥惧儚constobserver=newIntersectionObserver((entries,originalObserver)=>{entries.forEach(entry=>{if(entry.intersectionRatio>0||entry.isIntersecting){originalObserver.unobserve(entry.target)if(!isLoaded(entry.target)){load(entry.target)}}})},{...閫夐」,rootMargin:"0px",threshold:0,},)return{//璁剧疆瑙傚療鍥惧儚lazyObserver:()=>{consteles=document.querySelectorAll(".pika-lazy")for(consteleofArray.from(eles)){if(observer){observer.observe(ele)continue}if(isLoaded(ele))continueload(ele)}},}}PWAPWA鐨勭紦瀛樻帶鍒跺拰鏇存柊鑳藉姏浣跨敤workbox浣嗘槸澧炲姞浜嗗垹闄ょ紦瀛樼殑閫昏緫锛歩mport{cacheNames}from"workbox-core"constcurrentCacheNames={"whole-site":"whole-site","net-easy-p":"net-easy-p","api-banner":"api-banner","api-personalized-newsong":"api-personalized-newsong","api-playlist":"api-play-list","api-songs":"api-songs","api-albums":"api-albums","api-mvs":"api-mvs","api-music-check":"api-music-check",[cacheNames.precache]:cacheNames.precache,[cacheNames.runtime]:cacheNames.runtime,}self.addEventListener("婵€娲?,event=>{event.waitUntil(caches.keys().then(cacheGroup=>{returnPromise.all(cacheGroup.filter(cacheName=>{return!Object.values(currentCacheNames).includes(`${cacheName}`)}).map(cacheName=>{//鍒犻櫎涓庡綋鍓嶇紦瀛樹笉鍖归厤鐨勭紦瀛榬eturncaches.delete(cacheName)}),)}),)})椤圭洰鐨凱WA缂撳瓨鎺у埗绛栫暐涓昏閫夋嫨StaleWhileRevalidate锛屽厛鏄剧ず缂撳瓨锛堝鏋滄湁鐨勮瘽锛夛紝鐒跺悗pwa浼氭洿鏂扮紦瀛樸€傜敱浜庨」鐩娇鐢ㄤ簡swr锛屽綋椤甸潰浠庨殣钘忓彉涓烘樉绀烘椂锛屽簱浼氳疆璇㈤〉闈㈢殑鏁版嵁鎴栬€呰姹傛洿鏂版暟鎹紝浠庤€岃揪鍒颁娇鐢╬wa鏇存柊缂撳瓨鐨勭洰鐨勩€傛祻瑙堝櫒鍏煎IOS>=10锛孉ndriod>=6鏈€鍚庯紝濡傛灉瀵逛綘鐨剅eact瀛︿範鏈夊府鍔╋紝璇穝tar涓€涓媬github鍦板潃馃帀馃帀