当前位置:网站首页>Hot update scheme of Chrome extension program: 2. Based on double cache update function module

Hot update scheme of Chrome extension program: 2. Based on double cache update function module

2020-11-09 19:11:00 InfoQ

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" background "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Last article , This paper introduces the implementation principle of hot update scheme of extension program and Content-Scripts Build and deploy , The code is as follows , there hotFix Method , The replacement is the execution of the hot code . Get hot update code to version from interface , How to store and parse to ensure performance and correctness ?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Last one :"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/bb39fa0b70cd3b6b6da33ab8d","title":""},"content":[{"type":"text","text":"Chrome Hot update solutions for extensions :1. Principle analysis and construction deployment "}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// The function module executes the entry file \nimport hotFix from 'hotfix.js'\nimport obj from './entity.js'\n\n// Heat repair method , Yes obj Module for hot repair ( Introduction to the next issue : Get hot update code based on double cache )\nconst moduleName = 'obj';\nhotFix('moduleName').catch(err=>{\n console.warn(`${moduleName} Online code parsing failed `,err)\n obj.Init()\n})"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" One 、 Extender communication flow chart "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5c/5cbe5857947b401fa35452485aa58cc7.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"background.js: Background page , Running in the browser background , A separate process , The browser runs from open to close , For the extension program \" center \", Perform the main functions of the application ."}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"content-script(CS): Running on the Web Page context JavaScript file , One tab Produce a CS Environmental Science , It is associated with web The context of the page is insulated ."}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" be based on Chrome Communication process , Obviously, it is most reasonable to get the hot update code version in the background page for overall management ."}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" Two 、 The choice of storage mode "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Several common storage methods :\n"},{"type":"text","marks":[{"type":"strong"}],"text":"cookie"},{"type":"text","text":":  conversation , Every request is sent back to the server , Not larger than 4kb.\n"},{"type":"text","marks":[{"type":"strong"}],"text":"sessionStorage"},{"type":"text","text":":  Storage of session performance , The lifecycle is the current window or tab , When the window or tab is closed , The stored data is emptied .\n"},{"type":"text","marks":[{"type":"strong"}],"text":"localStorage"},{"type":"text","text":":  Record in memory , The life cycle is permanent , Unless the user takes the initiative to delete data .\n"},{"type":"text","marks":[{"type":"strong"}],"text":"indexedDB"},{"type":"text","text":": A local transactional database system , Used to store large data structures in browsers , And provide index function to achieve high-performance search ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LocalStorage Data is usually stored in 2.5MB~10MB Between ( Different browsers ),IndexedDB More storage , Generally not less than 250M, And IndexedDB With search function , And the ability to create a custom index . Considering the hot update code module more , Large size , And the local need to manage the hot update code according to the version , So choose IndexedDB As a storage solution ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IndexedDB Study address :"},{"type":"link","attrs":{"href":"http://www.ruanyifeng.com/blog/2018/07/indexeddb.html","title":""},"content":[{"type":"text","text":" Browser database IndexedDB Introductory tutorial "}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Attached is a simple implementation :"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/**\n * @param dbName Database name \n * @param version Database version The default value is 1\n * @param primary Database table primary key \n * @param indexList Array The fields of the database table and the configuration of the fields , Each item is Object, The structure is { name, keyPath, options }\n */\nclass WebDB{\n constructor({dbName, version, primary, indexList}){\n this.db = null\n this.objectStore = null\n this.request = null\n this.primary = primary\n this.indexList = indexList\n this.version = version\n this.intVersion = parseInt(version.replace(/\\./g, ''))\n this.dbName = dbName\n try {\n this.open(dbName, this.intVersion)\n } catch (e) {\n throw e\n }\n }\n\n open (dbName, version) {\n const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;\n if (!indexedDB) {\n console.error(' Your browser does not support it IndexedDB')\n }\n this.request = indexedDB.open(dbName, version)\n this.request.onsuccess = this.openSuccess.bind(this)\n this.request.onerror = this.openError.bind(this)\n this.request.onupgradeneeded = this.onupgradeneeded.bind(this)\n }\n\n onupgradeneeded (event) {\n console.log('onupgradeneeded success!')\n this.db = event.target.result\n const names = this.db.objectStoreNames\n if (names.length) {\n for (let i = 0; i< names.length; i++) {\n if (this.compareVersion(this.version, names[i]) !== 0) {\n this.db.deleteObjectStore(names[i])\n }\n }\n }\n if (!names.contains(this.version)) {\n // Create table , Configuring the primary key \n this.objectStore = this.db.createObjectStore(this.version, { keyPath: this.primary })\n this.indexList.forEach(index => {\n const { name, keyPath, options } = index\n // Create column , Configuration properties \n this.objectStore.createIndex(name, keyPath, options)\n })\n }\n }\n\n openSuccess (event) {\n console.log('openSuccess success!')\n this.db = event.target.result\n }\n\n openError (event) {\n console.error(' Database open error report ', event)\n // Relink the database \n if (event.type === 'error' && event.target.error.name === 'VersionError') {\n indexedDB.deleteDatabase(this.dbName);\n this.open(this.dbName, this.intVersion)\n }\n }\n\n compareVersion (v1, v2) {\n if (!v1 || !v2 || !isString(v1) || !isString(v2)) {\n throw ' Version parameter error '\n }\n const v1Arr = v1.split('.')\n const v2Arr = v2.split('.')\n if (v1 === v2) {\n return 0\n }\n if (v1Arr.length === v2Arr.length) {\n for (let i = 0; i< v1Arr.length; i++) {\n if (+v1Arr[i] > +v2Arr[i]) {\n return 1\n } else if (+v1Arr[i] === +v2Arr[i]) {\n continue\n } else {\n return -1\n }\n }\n }\n throw ' Version parameter error '\n }\n\n /**\n * Add records \n * @param record Structure and indexList Set down index Fields correspond to \n * @return Promise\n */\n add (record) {\n if (!record.key) throw ' Need to add key Is a required field !'\n return new Promise((resolve, reject) => {\n let request\n try {\n request = this.db.transaction([this.version], 'readwrite').objectStore(this.version).add(record)\n request.onsuccess = function (event) {\n resolve(event)\n }\n\n request.onerror = function (event) {\n console.error(`${record.key}, Data write failure `)\n reject(event)\n }\n } catch (e) {\n reject(e)\n }\n })\n }\n \n // Other code omissions \n ...\n ... \n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" 3、 ... and 、 Double cache to get hot update code "}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":" IndexedDB Modeling and storing interface data "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" The hot update module code is only related to the version , Build the table according to the version . Table primary key key:  Indicates the module name    Name value:  Indicates the module hot update code   "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" When the page function module , First request for hot update code , To be successful , Add data to the table . Next page request , From IndexedDB Table access , In order to reduce the number of queries on the interface , And the server IO operation ."}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" Background page global cache "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" Create global object cache module to hot update data , Instead of frequent IndexedDB Database operation ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" Attach simple code :"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\nlet DBRequest\nconst moduleCache = {}   //  Hot update function module cache \nconst moduleStatus = {}  //  Storage module status \n\n//  Interface to get hot update code , Update local database \nconst getLastCode = (moduleName, type) => {\n  const cdnUrl = 'https://***.com'\n  const scriptUrl = addParam(`${cdnUrl}/${version}/${type}/${moduleName}.js`, {\n    _: new Date().getTime()\n  })\n  return request.get({\n    url: scriptUrl\n  }).then(res => {\n    updateModuleCode(moduleName, res.trim())\n    return res.trim()\n  })    \n}\n\n//  Update local database \nconst updateModuleCode = (moduleName, code, dbRequest = DBRequest) => {\n  dbRequest.get(moduleName).then(record => {\n    if (record) {\n      dbRequest.update({key: moduleName,value: code}).then(() => {\n        moduleStatus[moduleName] = 'loaded'\n      }).catch(err => {\n        console.warn(` Data update ${moduleName} Failure !`, err)\n      })\n    }\n  }).catch(() => {\n    dbRequest.add({key: moduleName,value: code}).then(() => {\n      moduleStatus[moduleName] = 'loaded'\n    }).catch(err => {\n      console.warn(`${moduleName}  Failed to add database !`, err)\n    })\n  })\n  moduleCache[moduleName] = code\n}\n\n//  Get module hot update code \nconst getHotFixCode = ({moduleName, type}, sendResponse) => {\n  if (!DBRequest) {\n    try {\n      DBRequest = new WebDB({\n        dbName,\n        version,\n        primary: 'key',\n        indexList: [{ name: 'value', KeyPath: 'value', options: { unique: true } }]\n      })\n    } catch (e) {\n      console.warn(moduleName, ' : Failed to link database :', e)\n      return\n    }\n  } \n\n  //  There are cache objects \n  if (moduleCache[moduleName]) {\n    isFunction(sendResponse) && sendResponse({\n      status: 'success',\n      code: moduleCache[moduleName]\n    })\n    moduleStatus[moduleName] !== 'loaded' && getLastCode(moduleName, type)\n  }\n  else{ //  There is no cache object , From IndexDB take \n    setTimeout(()=>{\n      DBRequest.get(moduleName).then(res => {\n        ...\n        moduleStatus[moduleName] !== 'loaded' && getLastCode(moduleName, type)\n      }).catch(err => {\n        ...\n        moduleStatus[moduleName] !== 'loaded' && getLastCode(moduleName, type)\n      })\n    },0)\n  }\n}\n\nexport default getHotFixCode\n"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" Four 、CS Parsing hot update code "}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" Background page registration listen for hot update code request "}]}]}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// HotFix.js Background page encapsulation method \nimport moduleMap from 'moduleMap' //  As mentioned in the previous section , All function modules need to be registered \n\nclass HotFix {\n  constructor() {\n    //  Register listening requests   \n    chrome.extension.onRequest.addListener(this.requestListener)\n    //  Production environment  &  Heat repair environment  &  Test environment : The browser opens the default hot fix code for all configuration function modules \n    if (__PROD__ || __HOT__ || __TEST__) {\n      try {\n        this.getModuleCode()\n      }catch (e) {\n        console.warn(e)\n      }\n    }\n  }\n\n  requestListener (request, sender, sendResponse) {\n    switch(request.name) {\n      case 'getHotFixCode':\n        getHotFixCode(request, sendResponse)\n        break\n    }\n  }\n\n  getModuleCode () {\n    for (let p in moduleMap) {\n      getHotFixCode(...)\n    }\n  }\n}\n\nexport default new HotFix()\n \n// background.js  Register listening requests \nimport './HotFix'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"CS Send request to get data , And perform the update "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" The related simple code is as follows :"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// CS Of hotfix.js Parsing hot update code \nconst deepsFilterModule = [\n  'csCommon',\n  'Popup'\n]\n\nconst insertScript = (injectUrl, id, reject) => {\n  if (document.getElementById(id)) {\n    return\n  }\n\n  const temp = document.createElement('script');\n  temp.setAttribute('type', 'text/javascript');\n  temp.setAttribute('id', id)\n  temp.src = injectUrl\n  temp.onerror = function() {\n    console.warn(`pageScript ${id}, Online code parsing failed `)\n    reject()\n  }\n  document.head.appendChild(temp)\n}\n\nconst parseCode = (moduleName, code, reject) => {\n  try {\n    eval(code)\n    window.CRX[moduleName].init()\n  } catch (e) {\n    console.warn(moduleName + '  Parse failure : ', e)\n    reject(e)\n  }\n}\n\nfunction deepsReady(checkDeeps, execute, time = 100){\n  let exec = function(){\n    if(checkDeeps()){\n      execute();\n    }else{\n      setTimeout(exec,time);\n    }\n  }\n  setTimeout(exec,0);\n}\n\nconst hotFix = (moduleName, type = 'cs') => {\n  if (!moduleName) {\n    return Promise.reject(' Parameter error ')\n  }\n\n  return new Promise((resolve, reject) => {\n    //  Non-production environment  &  Heat repair environment  &  Test environment : Take the local code \n    if (!__PROD__ && !__HOT__ && !__TEST__) {\n      if (deepsFilterModule.indexOf(moduleName) > -1) {\n        reject()\n      } else {\n        deepsReady(\n          () => window.CRX && window.CRX.$COM && Object.keys(window.CRX.$COM).length,\n          reject\n        )\n      }\n      return\n    }\n\n    //  Send a hot update code request to the background page \n    chrome.extension.sendRequest({\n      name: \"getHotFixCode\",\n      type: type,\n      moduleName\n    }, function(res) {\n      if (res.status === 'success') {\n        if (type !== 'ps') {\n          //  Public methods 、Pop Page code , Parse code directly \n // Function module code , Wait for public method parsing to complete , To execute ,CS Reference public methods \n          if (deepsFilterModule.indexOf(moduleName) === -1) {\n            deepsReady(() => window.CRX && window.CRX.$COM && Object.keys(window.CRX.$COM).length, () => parseCode(moduleName, res.code, reject))\n          } else {\n            parseCode(moduleName, res.code, reject)\n          }\n        } else {\n          insertScript(res.code, moduleName, reject)\n        }\n      } else {\n        if (deepsFilterModule.indexOf(moduleName) === -1) {\n          deepsReady(() => window.CRX && window.CRX.$COM && Object.keys(window.CRX.$COM).length, () => reject(' Online code doesn't exist !'))\n        } else {\n          reject(' Online code doesn't exist !')\n        }\n      }\n    })\n  })\n}\n\nexport default hotFix"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" 5、 ... and 、 summary "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" Resume example , Completed the logic design of module function hot update ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢