Authored by 陈峰

兼容wkwebview的defer不生效,延迟js加载

@@ -14,7 +14,6 @@ const watcher = { @@ -14,7 +14,6 @@ const watcher = {
14 14
15 let renderer; 15 let renderer;
16 let realyPromise; 16 let realyPromise;
17 -let template = fs.readFileSync(path.join(__dirname, './apps/index.html'), 'utf-8');  
18 17
19 if (cluster.isMaster) { 18 if (cluster.isMaster) {
20 const masterApp = express(); 19 const masterApp = express();
@@ -22,7 +21,7 @@ if (cluster.isMaster) { @@ -22,7 +21,7 @@ if (cluster.isMaster) {
22 realyPromise = devServer(masterApp, params => { 21 realyPromise = devServer(masterApp, params => {
23 renderer = createBundleRenderer(params.bundle, Object.assign(params.options, { 22 renderer = createBundleRenderer(params.bundle, Object.assign(params.options, {
24 runInNewContext: false, 23 runInNewContext: false,
25 - template 24 + inject: false
26 })); 25 }));
27 }); 26 });
28 let childWorker = cluster.fork(); 27 let childWorker = cluster.fork();
@@ -34,7 +33,12 @@ if (cluster.isMaster) { @@ -34,7 +33,12 @@ if (cluster.isMaster) {
34 if (err) { 33 if (err) {
35 console.error(err); 34 console.error(err);
36 } 35 }
37 - worker.send({action: 'ssr_request', html, err: err && JSON.stringify(err)}); 36 + const styles = msg.context.renderStyles();
  37 + const scripts = msg.context.renderScripts();
  38 + const resources = msg.context.renderResourceHints();
  39 + const states = msg.context.renderState();
  40 +
  41 + worker.send({action: 'ssr_request', html, styles, scripts, resources, states, err: err && JSON.stringify(err)});
38 }); 42 });
39 }); 43 });
40 } 44 }
@@ -9,6 +9,7 @@ const routes = require('../../config/ssr-routes'); @@ -9,6 +9,7 @@ const routes = require('../../config/ssr-routes');
9 const redis = require('../../utils/redis'); 9 const redis = require('../../utils/redis');
10 const routeEncode = require('../../utils/route-encode'); 10 const routeEncode = require('../../utils/route-encode');
11 const {createBundleRenderer} = require('vue-server-renderer'); 11 const {createBundleRenderer} = require('vue-server-renderer');
  12 +const Handlebars = require('handlebars');
12 const logger = global.yoho.logger; 13 const logger = global.yoho.logger;
13 const config = global.yoho.config; 14 const config = global.yoho.config;
14 15
@@ -18,8 +19,11 @@ let renderer; @@ -18,8 +19,11 @@ let renderer;
18 let serverBundle; 19 let serverBundle;
19 let degradeHtml; 20 let degradeHtml;
20 21
  22 +const hbs = fs.readFileSync(path.join(__dirname, '../views/index.hbs'), 'utf-8');
  23 +
  24 +const template = Handlebars.compile(hbs);
  25 +
21 if (!isDev) { 26 if (!isDev) {
22 - const template = fs.readFileSync(path.join(__dirname, '../../index.html'), 'utf-8');  
23 27
24 degradeHtml = fs.readFileSync(path.join(__dirname, '../../degrade.html'), 'utf-8'); 28 degradeHtml = fs.readFileSync(path.join(__dirname, '../../degrade.html'), 'utf-8');
25 29
@@ -28,11 +32,28 @@ if (!isDev) { @@ -28,11 +32,28 @@ if (!isDev) {
28 32
29 renderer = createBundleRenderer(serverBundle, { 33 renderer = createBundleRenderer(serverBundle, {
30 runInNewContext: false, 34 runInNewContext: false,
31 - template,  
32 - clientManifest 35 + clientManifest,
  36 + inject: false
33 }); 37 });
34 } 38 }
35 39
  40 +const REG_SCRIPT = /src="([^"]+)"/g;
  41 +
  42 +const asyncLoadScripts = (renderScripts) => {
  43 + let match;
  44 + const scripts = [];
  45 +
  46 + while ((match = REG_SCRIPT.exec(renderScripts))) {
  47 + scripts.push({
  48 + src: match[1],
  49 + index: scripts.length
  50 + });
  51 + }
  52 +
  53 + return scripts;
  54 +};
  55 +
  56 +
36 const getContext = (req) => { 57 const getContext = (req) => {
37 return { 58 return {
38 url: req.url, 59 url: req.url,
@@ -103,13 +124,33 @@ const render = (route) => { @@ -103,13 +124,33 @@ const render = (route) => {
103 let context = getContext(req); 124 let context = getContext(req);
104 125
105 renderer.renderToString(context, (err, html) => { 126 renderer.renderToString(context, (err, html) => {
  127 +
106 if (err) { 128 if (err) {
107 return handlerError(err, req, res, next); 129 return handlerError(err, req, res, next);
108 } 130 }
  131 + const styles = context.renderStyles();
  132 + let scripts = context.renderScripts();
  133 + const resources = context.renderResourceHints();
  134 + const states = context.renderState();
  135 + let asyncScripts;
  136 +
  137 + if (req.yoho.isiOS) {
  138 + asyncScripts = asyncLoadScripts(scripts);
  139 + }
  140 +
  141 + const result = template({
  142 + html,
  143 + styles,
  144 + scripts,
  145 + asyncScripts,
  146 + resources,
  147 + states
  148 + });
  149 +
109 if (config.useCache && route.cache && ck) { 150 if (config.useCache && route.cache && ck) {
110 - redis.setex(ck, route.cacheTime || 60, html); 151 + redis.setex(ck, route.cacheTime || 60, result);
111 } 152 }
112 - return res.send(html); 153 + return res.send(result);
113 }); 154 });
114 } catch (error) { 155 } catch (error) {
115 return next(error); 156 return next(error);
@@ -158,10 +199,21 @@ const devRender = (route) => { @@ -158,10 +199,21 @@ const devRender = (route) => {
158 } catch (error) {} // eslint-disable-line 199 } catch (error) {} // eslint-disable-line
159 return handlerError(err, req, res, next); 200 return handlerError(err, req, res, next);
160 } 201 }
  202 + let {styles, scripts, resources, states, html} = msg;
  203 +
  204 + const result = template({
  205 + html,
  206 + styles,
  207 + scripts,
  208 + resources,
  209 + states
  210 + });
  211 +
  212 +
161 if (config.useCache && route.cache && ck) { 213 if (config.useCache && route.cache && ck) {
162 - redis.setex(ck, route.cacheTime || 60, msg.html); 214 + redis.setex(ck, route.cacheTime || 60, result);
163 } 215 }
164 - return res.end(msg.html); 216 + return res.end(result);
165 } 217 }
166 }; 218 };
167 219
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="utf-8">
  5 + <title>{{title}}</title>
  6 + <meta name="keywords" content="">
  7 + <meta name="description" content="">
  8 + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover">
  9 + <meta name="apple-mobile-web-app-status-bar-style" content="black">
  10 + <meta content="yes" name="apple-mobile-web-app-capable">
  11 + <meta content="telephone=no" name="format-detection">
  12 + <meta content="email=no" name="format-detection">
  13 + {{{resources}}}
  14 + {{{styles}}}
  15 + <script type="text/javascript">
  16 + (function(d,c){var e=d.documentElement,a="orientationchange" in window?"orientationchange":"resize",b=function(){var f=e.clientWidth;if(!f){return}if(f>=750){e.style.fontSize="40px"}else{e.style.fontSize=40*(f/750)+"px"}};if(!d.addEventListener){return}b();c.addEventListener(a,b,false);d.addEventListener("DOMContentLoaded",b,false)})(document,window);
  17 + </script>
  18 +</head>
  19 +<body>
  20 + {{{html}}}
  21 + <div id="degrade-app"></div>
  22 + <div id="main-wrap">
  23 + <div id="no-download"></div>
  24 + </div>
  25 + {{{states}}}
  26 + {{#if asyncScripts}}
  27 + <script>
  28 + document.addEventListener('DOMContentLoaded', function() {
  29 + console.log('DOMContentLoaded')
  30 + setTimeout(function() {
  31 + var s = document.getElementsByTagName("script")[0];
  32 + {{# asyncScripts}}
  33 + var hm{{index}} = document.createElement("script");
  34 + hm{{index}}.async = true;
  35 + hm{{index}}.src = "{{src}}";
  36 + s.parentNode.insertBefore(hm{{index}}, s);
  37 + {{/ asyncScripts}}
  38 + }, 200)
  39 + })
  40 + </script>
  41 + {{^}}
  42 + {{{scripts}}}
  43 + {{/if}}
  44 + <script>
  45 + setTimeout(function() {
  46 + (function(w, d, s, j, f) {
  47 + var a = d.createElement(s);
  48 + var m = d.getElementsByTagName(s)[0];
  49 +
  50 + w.YohoAcquisitionObject = f;
  51 +
  52 + w[f] = function() {
  53 + w[f].p = arguments;
  54 + };
  55 +
  56 + a.async = 1;
  57 + a.src = j;
  58 + m.parentNode.insertBefore(a, m);
  59 + }(window, document, 'script', (document.location.protocol === 'https:' ? 'https:' : 'http:') + '//cdn.yoho.cn/yas-jssdk/2.4.18/yas.js', '_yas'));
  60 +
  61 + var _hmt = _hmt || [];
  62 +
  63 + (function() {
  64 + function getUid() {
  65 + var uid,
  66 + name = 'app_uid',
  67 + cookies = (document.cookie && document.cookie.split(';')) || [];
  68 +
  69 + for (var i = 0; i < cookies.length; i++) {
  70 + if (cookies[i].indexOf(name) > -1) {
  71 + uid = decodeURIComponent(cookies[i].replace(name + '=', '').trim());
  72 + break;
  73 + }
  74 + }
  75 +
  76 + if (!uid) return 0;
  77 +
  78 + uid = uid.split('::');
  79 + if (!uid || uid.length < 4) {
  80 + return 0;
  81 + }
  82 + return uid[1];
  83 + }
  84 +
  85 + function queryString() {
  86 + var vars = {},
  87 + hash,
  88 + i;
  89 + var hashes = window.location.search.slice(1).split('&');
  90 +
  91 + for (i = 0; i < hashes.length; i++) {
  92 + hash = hashes[i].split('=');
  93 + vars[hash[0]] = hash[1];
  94 + }
  95 + return vars;
  96 + }
  97 +
  98 + var uid = getUid() || queryString().uid;
  99 +
  100 + uid = uid === 0 ? '' : uid;
  101 + window._ozuid = uid; // 暴露ozuid
  102 + if (window._yas) {
  103 + window._yas(1 * new Date(), '2.4.16', 'yohoappweb', uid, '', '');
  104 + }
  105 +
  106 + (function() {
  107 + var hm = document.createElement("script");
  108 + hm.src = "https://hm.baidu.com/hm.js?65dd99e0435a55177ffda862198ce841";
  109 + var s = document.getElementsByTagName("script")[0];
  110 + s.parentNode.insertBefore(hm, s);
  111 + })();
  112 + }());
  113 + }, 500);
  114 + </script>
  115 +</body>
  116 +</html>