{"id":89,"date":"2019-10-05T15:03:30","date_gmt":"2019-10-05T12:03:30","guid":{"rendered":"https:\/\/guoyunhe.me\/en\/?p=89"},"modified":"2020-02-07T00:07:56","modified_gmt":"2020-02-06T22:07:56","slug":"create-react-webextension-for-firefox-and-chrome","status":"publish","type":"post","link":"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/","title":{"rendered":"Create React WebExtension for Firefox and Chrome"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">We will create a React app and turn it into a Firefox\/Chrome extension.<\/p>\n\n\n\n<!--more-->\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_87_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#What_is_the_difference_between_web_apps_and_browser_extensions\" >What is the difference between web apps and browser extensions?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Create_React_app\" >Create React app<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Modify_manifest_file\" >Modify manifest file<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Custom_build_configuration\" >Custom build configuration<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Add_toolbar_icon_and_popup\" >Add toolbar icon and popup<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Build_and_test_in_browser\" >Build and test in browser<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Add_sidebar\" >Add sidebar<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/guoyunhe.me\/en\/2019\/10\/05\/create-react-webextension-for-firefox-and-chrome\/#Add_developer_tools_panel\" >Add developer tools panel<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_is_the_difference_between_web_apps_and_browser_extensions\"><\/span>What is the difference between web apps and browser extensions?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Browser extensions are also based on web technology, with several differences than regular web app.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>It is loaded into popups, sidebars, configuration page or a devtool panel.<\/li><li>It can use WebExtension APIs.<\/li><li>Usually, it is not restricted by CORS.<\/li><li>It is loaded through <code>manifest.json<\/code>.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">WebExtension API is created by Chrome. Then Firefox turned to WebExtension API, too. Other Chromium based browsers, like Opera and Edge, also support it. So WebExtension API is the actual standard of browser extension. The extension we created should work on most desktop browsers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Create_React_app\"><\/span>Create React app<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It is hard to configure a React project from scratch. So many developers use <code>create-react-app<\/code>. It can also be used to create a WebExtension.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">npx create-react-app foobar-extension --typescript --use-npm<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The command above creates a React app built with NPM and TypeScript. You can change those options as you want.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Modify_manifest_file\"><\/span>Modify manifest file<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We already have the <code>public\/manifest.json<\/code> file. It is for Progressive Web Application. Manifest file for WebExtension is a little bit different. We need to change it to the following format:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  \"manifest_version\": 2,\n  \"name\": \"Foobar\",\n  \"version\": \"1.0\",\n  \"description\": \"Foobar extension made with React.\",\n  \"icons\": {\n    \"192\": \"logo192.png\",\n    \"512\": \"logo512.png\"\n  }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Custom_build_configuration\"><\/span>Custom build configuration<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create <code>.env<\/code> file at project root with the following content:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-ini\">INLINE_RUNTIME_CHUNK=false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This prevent inline JavaScript which breaks Content Security Policy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Add_toolbar_icon_and_popup\"><\/span>Add toolbar icon and popup<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add the following lines in <code>public\/manifest.json<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  ...\n  \"browser_action\": {\n    \"default_icon\": \"logo192.png\",\n    \"default_title\": \"Foobar\",\n    \"default_popup\": \"index.html\"\n  }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Build_and_test_in_browser\"><\/span>Build and test in browser<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run <code>npm run build<\/code>. The extension will be built in <code>build<\/code> folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take Firefox as example, open <code>about:debugging<\/code> in address bar. Then click the \"Load Temporary Add-on\" button. Open <code>build\/manifest.json<\/code> file under your project folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After loading the extension, you could see a new icon in the browser toolbar. Click it, you should see a small popup showing.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"347\" height=\"226\" src=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image.png\" alt=\"\" class=\"wp-image-90\" srcset=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image.png 347w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-300x195.png 300w\" sizes=\"auto, (max-width: 347px) 100vw, 347px\" \/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Add_sidebar\"><\/span>Add sidebar<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sidebar is more persistent than popup. If users need to interact with between web page and extension view very often, sidebar is a better choice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Similar to popup, you need to add configuration to <code>public\/manifest.json<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  ...\n  \"sidebar_action\": {\n    \"default_icon\": \"logo192.png\",\n    \"default_title\": \"AC Design Insight\",\n    \"default_panel\": \"index.html\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Build the app again. Go to <code>about:debugging<\/code> and reload the extension. You should see a nice sidebar appear.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"531\" src=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-3-1024x531.png\" alt=\"\" class=\"wp-image-93\" srcset=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-3-1024x531.png 1024w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-3-300x156.png 300w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-3-768x398.png 768w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-3.png 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Add_developer_tools_panel\"><\/span>Add developer tools panel<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If your extension is for web development, developer tools is where you load the app. First, insert another line to <code>public\/manifest.json<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n  ...\n  \"devtools_page\": \"devtools.html\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then, create <code>public\/devtools.html<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-markup\">&lt;html>\n&lt;head>\n  &lt;meta charset=\"utf-8\" \/>\n&lt;\/head>\n&lt;body>\n  &lt;script src=\"devtools.js\">&lt;\/script>\n&lt;\/body>\n&lt;\/html><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then create <code>public\/devtools.js<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-javascript\">browser.devtools.panels.create(\n  \"Foobar\",\n  \"\/logo192.png\",\n  \"\/index.html\"\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Build the app again. Go to <code>about:debugging<\/code> and reload the extension.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Press <code>F12<\/code> and you should see a new tab \"Foobar\" in developer tools.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"531\" src=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-2-1024x531.png\" alt=\"\" class=\"wp-image-92\" srcset=\"https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-2-1024x531.png 1024w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-2-300x156.png 300w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-2-768x398.png 768w, https:\/\/guoyunhe.me\/en\/wp-content\/uploads\/sites\/2\/2019\/10\/image-2.png 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The full code is available on <a href=\"https:\/\/github.com\/guoyunhe\/react-webextension-example\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We will create a React app and turn it into a Firefox\/Chrome extension.<\/p>\n","protected":false},"author":1,"featured_media":96,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[5,9],"class_list":["post-89","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","tag-react","tag-web-extension"],"_links":{"self":[{"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/posts\/89","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/comments?post=89"}],"version-history":[{"count":9,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/posts\/89\/revisions"}],"predecessor-version":[{"id":124,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/posts\/89\/revisions\/124"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/media\/96"}],"wp:attachment":[{"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/media?parent=89"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/categories?post=89"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/guoyunhe.me\/en\/wp-json\/wp\/v2\/tags?post=89"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}