Background
Recently, I started a toy project of building a Chrome Extension helper tool for myself. As a rookie developer, it took me a while to set up the project. So I think it is better to document it with this post as a learning note.
Why Parcel + Preact + TailwindCSS stack?
Preact and TailwindCSS are light-weight, which is good for a small app like Chrome Extension. I never try Parcel before, but it is claimed that it is having a better benchmark than the popular Webpack. To be honest I am not sure about that, but the zero configuration of Parcel really got me. So, why not give it a go?
Also feel free to check their official site from the above links for more details.
Begin the setup
Before the setup, I have prepared all the files mentioned in this post in this repo, though we are about to go through them.
Let's begin the setup! Everything starts from an empty directory.
1. Prepare basic files package.json
and manifest.json
Init a package.json
with yarn, add flag -y
to default answer yes to all questions:
yarn init -y
Then create manifest.json
:
{
"name": "Chrome Extension boilerplate",
"version": "1.0",
"description": "Chrome Extension boilerplate with Parcel + Preact + TailwindCSS",
"manifest_version": 2,
"browser_action": {
"default_icon": "static/icon_32.png"
},
"icons": {
"48": "static/icon_32.png"
},
"permissions": [
"tabs",
"contextMenus"
],
"background": {
"scripts": ["./src/background.js"]
},
"options_ui": {
"page": "./src/options.html",
"open_in_tab": true
}
}
also create other three files /src/background.js
, /src/options.html
, /src/main.html
with very simple content
/src/background.js
:
// /src/background.js
// create context menu item, with simple onclick event that brings users to the main.html
chrome.contextMenus.create({
id: "main_page",
title: "Open Main Page",
onclick: () => {
chrome.tabs.create({
url: chrome.runtime.getURL("src/main.html")
});
},
contexts: ["browser_action"]
}, () => {});
/src/main.html
:
<!-- /src/main.html -->
<!DOCTYPE html>
<html>
<head>
<title>Main</title>
</head>
<body>
this is main page
</body>
</html>
/src/options.html
:
<!-- /src/options.html -->
<!DOCTYPE html>
<html>
<head>
<title>Options</title>
</head>
<body>
this is options page
</body>
</html>
2. Set up Parcel
add parcel package:
yarn add parcel-bundler --dev
(optional)add helper package for copying static files to generated files' destination:
yarn add parcel-plugin-static-files-copy --dev
Then add "scripts"
and "staticFiles"
to /package.json
:
{
"name": "setup-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"clear": "rm -rf dist .cache",
"watch": "yarn clear && parcel watch src/{main.html,options.html,background.js} -d dist/src --public-url ./ -t node --bundle-node-modules",
"build": "yarn clear && parcel build src/{main.html,options.html,background.js} -d dist/src --public-url ./"
},
"staticFiles": {
"staticPath": [
{
"staticPath": "static",
"staticOutDir": "../static/"
},
{
"staticPath": "manifest.json",
"staticOutDir": "../"
}
]
},
"devDependencies": {
"parcel-bundler": "^1.12.4",
"parcel-plugin-static-files-copy": "^2.5.0",
}
}
Note: if you decide not to install the static files plugin, just do it yourself by changing the "scripts"
to:
"scripts": {
"clear": "rm -rf dist .cache",
"watch": "yarn clear && mkdir dist && cp -R static dist/static && cp manifest.json dist/ && parcel watch src/{records.html,options.html,background.js} -t node --bundle-node-modules -d dist/src --public-url ./",
"build": "yarn clear && parcel build src/{records.html,options.html,background.js} -d dist/src/ --public-url ./ && cp -R static dist/static && cp manifest.json dist/"
},
Test the setup
So now if we run:
yarn build
a /dist
folder will be generated, go to browsers' extensions page(chrome://extensions/
for Chrome browser) and "Load unpacked", then select the /dist
folder
Check if the code is working:
i. load unpacked of the /dist folder in Extension page
ii. open extension context menu and click this "Open Main Page"
iii. check the content and celebrate๐
3. Set up Preact
add preact package:
yarn add preact
then add below "alias"
to package.json
:
"alias": {
"react": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat"
}
create file .babelrc
:
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "h",
"pragmaFrag": "Fragment"
}
]
]
}
finally create a file /src/js/app.js
:
// /src/js/app.js
import { h, render, Component } from "preact";
let Header = () => {
return (
<header>This is header by Preact</header>
)
}
document.addEventListener("DOMContentLoaded", () => {
render(<Header />, document.body);
});
and in main.html
if we change the body to:
<body>
<script src="./js/app.js"></script>
</body>
rebuild and check again๐๐
4. Set up TailwindCSS
add tailwindcss package:
yarn add tailwindcss --dev
init config files for tailwindcss:
npx tailwindcss init -p
(might have to yarn global add tailwindcss
before this)
then you would find postcss.config.js
and tailwind.config.js
generated:
// /postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
you will want to uncomment the purgeLayersByDefault: true,
and also add
purge: [
'./src/**/*.js',
],
into tailwind.config.js
like this:
// /tailwind.config.js
module.exports = {
future: {
// removeDeprecatedGapUtilities: true,
purgeLayersByDefault: true,
},
purge: [
'./src/**/*.js',
],
theme: {
extend: {},
},
variants: {},
plugins: [],
}
It's almost done, but I would love to write nested css for a more neater stylesheet. However, I failed at setting up the postcss plugin postcss-nested
because of reasons. Glad to be a failure this time, I soon realized Parcel has built-in support for LESS!
so we just have to go ahead and create the /src/style/app.less
:
/* /src/style/app.less */
@tailwind base;
@tailwind components;
/* remember to put customize css before importing the tailwind utilities */
body {
header { /* test nested feature */
@apply flex items-center justify-center text-lg; /* test tailwind utilities */
}
}
@tailwind utilities;
also add some styles to the preact <header>
to see if it works in app.js
:
<header class="h-24 bg-blue-200">This is header by Preact</header>
don't forget to link the stylesheet into the main.html
:
<link rel="stylesheet" href="./style/app.less">
let's do our final testing, flexbox works + text is large + background color pale blue and height is right, awesome๐๐๐
Issues and Reflection
Issue 1: Multiple Entry Point
So usually a bundle tool takes a single entry point for compilation, so do most of the Chrome Extension tutorial on the internet. Because the manifest.json
has defined the file path, including the main.html
, options.js
and background.js
, so I think I have make them all as entry points. It took me quite some time to figure out how to do it, so even though I am not actually doing anything meaningful with the options.html
during the above setup, I would like to document it since it is one of my big lesson here.
Issue 2: The Parcel version now does not support the latest PostCSS version 8
It was putting me on trouble, it limited me on using various PostCSS plugins like postcss-nested
/postcss-purgecss
/postcss-import
. I tried using older version of these plugin but it was strangely not working. Then I figure out different way of doing the nesting CSS: Using LESS! Parcel native support for LESS is just amazing. Also we can do CSS purging by simple configuration setting in tailwind.config.js
now in the latest version of TailwindCSS, life saver.
Bonus Issue 3: Develop in Windows 10
Usually I do development by Mac at my workplace, but at home I use Windows, which caused a lot of troubles even when installing the packages. So what I do to overcome this, is using Docker.
With Dockerfile
:
# pull official base image
FROM node:10-slim
WORKDIR /app
# install parcel
RUN yarn global add parcel-bundler
# install app dependencies
COPY package.json ./
RUN yarn install
# add app
COPY . ./
# Windows steps:
# Step 1: from host to container
# docker cp . demo-container:app/
# Step 2: build in container
# yarn build
# Step 3: from container to host
# docker cp demo-container:app/dist .
I use it like a VM just to compile the /dist
, though it might not be convenient to copy files back and forth. Good enough for a noob programmer I think.
Thanks for reading such a long post.
My way of setting up the project is absolutely not perfect at all, and IMO it is kinda silly on the Docker part.
Welcome to leave your comments and feedback, they will help me a lot for becoming a better developer!๐๐
Top comments (0)