Browser extensions are gaining more and more popularity recently. They're not a simple mini web applications any more, in most cases they are well tailored, profitable products used by hundreds of users every day. And once they grow in size, it's worth to consider building them using some helpful JavaScript libraries.
In this article, we will create a simple Chrome extension, that will be responsible for displaying recent top posts from DEV Community. For this, we will use Preact bootstrapped with Vite build tool.
Here's a little sneak peek 👀
Stack
Before we start, let's talk about the tech-stack that we will use.
Vite
If you're not familiar with it already, Vite is a fast and simple build tool for the web. Basically it makes things easier if it's about starting a new project, it's superfast and offers a lot of pre-defined templates, so you don't have to worry about configuring webpack, transpiling SCSS to CSS etc.
Preact
Preact is the JavaScript library, as the docs states it's:
Fast 3kB alternative to React with the same modern API
Of course, there are some differences between these two libraries, but they are not that crucial and if you're familiar with React you should quickly figure out how Preact works.
Code
First we need to initialize our project with Vite, we can do this by running the following command 👇
yarn create vite dev-articles-extension --template preact-ts
As you can see, our project name is dev-articles-extension and we used preact-ts preset since we want to use Preact with TypeScript. Running this command will create a directory with all necessary files to start working on a front-end application.
Now let's navigate to our project, install required dependencies, run code in development mode, navigate to http://localhost:3000/ and enjoy the magic 🪄
cd dev-articles-extension && yarn && yarn dev
Time for some code. We need to fetch recent top posts from DEV API and display them in a list, also we need to handle loading and error states, so let's do it. Replace app.tsx file with the following code 👇
import{useEffect,useState}from"preact/hooks";typeArticle={id:string;title:string;url:string;positive_reactions_count:number;published_timestamp:string;reading_time_minutes:number;};constuseArticles=()=>{const[articles,setArticles]=useState<Article[]>([]);const[error,setError]=useState("");const[isLoading,setLoading]=useState(true);useEffect(()=>{constfetchArticles=async ()=>{try{constresponse=awaitfetch("https://dev.to/api/articles?top=1");if (!response.ok){thrownewError("Response is not ok");}constdata=awaitresponse.json();setArticles(data);}catch (error){setError("An error ocurred while fetching articles");}finally{setLoading(false);}};fetchArticles();},[]);return{articles,error,isLoading};};exportconstApp=()=>{const{articles,error,isLoading}=useArticles();return (<divclassName="container">{isLoading?(<divclassName="spinner"><spanclassName="spinner__circle"/><span>Please wait...</span></div>):error?(<spanclassName="error">{error}</span>):(<><h1className="title">Top posts on DEV Community</h1><ulclassName="articles">{articles.map(({id,title,url,positive_reactions_count,published_timestamp,reading_time_minutes,})=>(<likey={id}className="article"><ahref={url}target="_blank"rel="noreferrer"className="article__link">{title}</a><ulclassName="article__details">{[{title:"Published at",icon:"🗓",label:"Calendar emoji",value:newDate(published_timestamp).toLocaleDateString(),},{title:"Reading time",icon:"🕑",label:"Clock emoji",value:`${reading_time_minutes} min`,},{title:"Reactions count",icon:"❤️ 🦄 🔖",label:"Heart, unicorn and bookmark emojis",value:positive_reactions_count,},].map(({title,icon,label,value},index)=>(<likey={`${id}-detail-${index}`}className="article__detail"title={title}><spanrole="img"aria-label={label}>{icon}</span><span>{value}</span></li>))}</ul></li>))}</ul></>)}</div>);};
This code is pretty self-explanatory, but if any part of it is unclear to you, let me know in the comments.
Application logic is ready, now it's time for some styling. Nothing crazy, just replace index.css file with this content 👇
At this point, you should have a fully functional application, but weren't we supposed to build a Chrome extension? We are one step away from that. Let's create a manifest file that will provide important information about our extension to the Chrome browser.
touch src/manifest.json
And fill it with required values 👇
{"manifest_version":3,"name":"DEV Articles","description":"A quick way to browse top posts from DEV Community.","version":"0.0.1","action":{"default_popup":"index.html"}}
Now we're ready to build our extension.
Building and installing
Vite provides us with build command that creates dist/ directory with all compiled files, but we also need to remember about copying src/manifest.json file there. In order to avoid doing this "by hand" every time we build our project, we will add build-extension script to the package.json that will do it automatically for us.
After running this command, you should see dist/ directory with manifest.json file in it. Now, let's navigate to chrome://extensions panel and upload dist/ directory there like so 👇
Viola, that's it! Your extension is ready to use.
Repository
I didn't prepare any live demo of this extension, but if you want to take a look at the full code, check out this repository on GitHub. Feel free to contribute.
I need to update my extension to some better library/tool/framework. My extension manage tabs, so I have to develop it while it is installed in the browser.
But besides Webpack no other good tool emits files during dev phase.
Any idea about it?
You've tagged TypeScript but there's no explanation on how to add extension development types. Only a -ts Vite template will not do miracles to add the right types.
You're right, good point. I forgot about it because I didn't need anything from Chrome API in this example. Try running npm i -D @types/chrome or yarn add --dev @types/chrome, it should do the job.
Thanks, but I've done this and it doesn't work. I'm using VSCode and the intellisense is completely unaware of any chrome types. Can you try this yourself too in a quick example project?
you need to update your manifest.json and add service_worker please refer to this document below for basics then ask chat gpt if any questions. developer.chrome.com/docs/extensio...
I need to update my extension to some better library/tool/framework. My extension manage tabs, so I have to develop it while it is installed in the browser.
But besides Webpack no other good tool emits files during dev phase.
Any idea about it?