{"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>We will create a React app and turn it into a Firefox\/Chrome extension.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">What is the difference between web apps and browser extensions?<\/h2>\n\n\n\n<p>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>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\">Create React app<\/h2>\n\n\n\n<p>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>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\">Modify manifest file<\/h2>\n\n\n\n<p>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\">Custom build configuration<\/h2>\n\n\n\n<p>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>This prevent inline JavaScript which breaks Content Security Policy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Add toolbar icon and popup<\/h2>\n\n\n\n<p>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\">Build and test in browser<\/h2>\n\n\n\n<p>Run <code>npm run build<\/code>. The extension will be built in <code>build<\/code> folder.<\/p>\n\n\n\n<p>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>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\">Add sidebar<\/h2>\n\n\n\n<p>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>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>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\">Add developer tools panel<\/h2>\n\n\n\n<p>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>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>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>Build the app again. Go to <code>about:debugging<\/code> and reload the extension.<\/p>\n\n\n\n<p>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>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}]}}