├── .all-contributorsrc
├── .eslintrc.js
├── .github
├── FUNDING.yml
├── actions
│ └── install-deps
│ │ └── action.yml
└── workflows
│ └── workflow-config.yml
├── .gitignore
├── .husky
├── pre-commit
└── pre-push
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc.json
├── .vscode
└── extensions.json
├── LICENSE
├── PRIVACY_POLICY.txt
├── README.md
├── babel.config.js
├── jest.config.js
├── jest.setup.js
├── package.json
├── pnpm-lock.yaml
├── public
├── chrome_manifest.json
├── edge_manifest.json
├── firefox_manifest.json
├── images
│ ├── afternoon.png
│ ├── logo
│ │ ├── date-time-128.png
│ │ ├── date-time-16.png
│ │ ├── date-time-32.png
│ │ └── date-time-48.png
│ ├── morning.png
│ └── night.png
├── index.html
└── popup.html
├── src
├── assets
│ └── css
│ │ └── global.css
├── background
│ ├── chrome
│ │ └── background.ts
│ ├── edge
│ │ └── background.ts
│ └── firefox
│ │ └── background.ts
├── components
│ ├── App
│ │ ├── App.svelte
│ │ └── __test
│ │ │ ├── App.spec.ts
│ │ │ └── __snapshots__
│ │ │ └── App.spec.ts.snap
│ ├── AppWrapper
│ │ ├── AppWrapper.svelte
│ │ └── __test
│ │ │ └── AppWrapper.spec.ts
│ └── DateTime
│ │ ├── DateTime.svelte
│ │ └── __test
│ │ └── DateTime.ts
├── lib
│ ├── __test
│ │ ├── get-display-datetime.spec.ts
│ │ └── get-palette.spec.ts
│ ├── get-display-datetime.ts
│ └── get-palette.ts
├── popup.ts
└── type
│ └── index.type.ts
├── svelte.config.js
├── tsconfig.json
└── webpack
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "davidnguyen179",
10 | "name": "David Nguyen",
11 | "avatar_url": "https://avatars.githubusercontent.com/u/6290720?v=4",
12 | "profile": "https://www.dzungnguyen.dev",
13 | "contributions": [
14 | "code",
15 | "doc",
16 | "design",
17 | "ideas",
18 | "infra",
19 | "maintenance",
20 | "review",
21 | "test"
22 | ]
23 | },
24 | {
25 | "login": "conandk",
26 | "name": "DK",
27 | "avatar_url": "https://avatars.githubusercontent.com/u/12934183?v=4",
28 | "profile": "https://github.com/conandk",
29 | "contributions": [
30 | "code",
31 | "ideas",
32 | "infra",
33 | "maintenance",
34 | "review",
35 | "test"
36 | ]
37 | }
38 | ],
39 | "contributorsPerLine": 7,
40 | "projectName": "web-extension-svelte-boilerplate",
41 | "projectOwner": "davidnguyen179",
42 | "repoType": "github",
43 | "repoHost": "https://github.com",
44 | "skipCi": true
45 | }
46 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | module.exports = {
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:import/errors',
11 | 'plugin:import/warnings',
12 | ],
13 | parser: '@typescript-eslint/parser',
14 | parserOptions: {
15 | ecmaVersion: 12,
16 | sourceType: 'module',
17 | },
18 | plugins: ['@typescript-eslint', 'svelte3', 'import'],
19 | overrides: [
20 | {
21 | files: ['*.svelte'],
22 | processor: 'svelte3/svelte3',
23 | },
24 | ],
25 | rules: {
26 | 'linebreak-style': ['error', 'unix'],
27 | quotes: ['error', 'single'],
28 | semi: ['error', 'always'],
29 | 'eol-last': ['error', 'always'],
30 | 'import/newline-after-import': [
31 | 'error',
32 | {
33 | count: 1,
34 | },
35 | ],
36 | '@typescript-eslint/explicit-function-return-type': 'off',
37 | '@typescript-eslint/explicit-module-boundary-types': 'off',
38 | 'no-console': 'error',
39 | '@typescript-eslint/ban-ts-comment': 'off',
40 | '@typescript-eslint/no-inferrable-types': 'off',
41 | '@typescript-eslint/no-unused-vars': 'error',
42 | },
43 | ignorePatterns: ['*.config.js', 'jest.setup.js'],
44 | settings: {
45 | 'import/resolver': {
46 | node: {
47 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
48 | },
49 | },
50 | 'svelte3/typescript': true,
51 | },
52 | };
53 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: paypal.me/davidnguyen179
--------------------------------------------------------------------------------
/.github/actions/install-deps/action.yml:
--------------------------------------------------------------------------------
1 | runs:
2 | using: 'composite'
3 | steps:
4 | - uses: pnpm/action-setup@v2
5 | with:
6 | version: 7
7 | - name: Setup Node
8 | uses: actions/setup-node@v3
9 | with:
10 | node-version-file: '.nvmrc'
11 | cache: 'pnpm'
12 |
--------------------------------------------------------------------------------
/.github/workflows/workflow-config.yml:
--------------------------------------------------------------------------------
1 | name: web-extension-svelte-boilerplate ci/cd
2 | on:
3 | push:
4 | jobs:
5 | installation:
6 | runs-on: ubuntu-20.04
7 | steps:
8 | - name: Checkout
9 | uses: actions/checkout@v3
10 | - name: Install dependencies
11 | uses: ./.github/actions/install-deps
12 | - name: Installing packages...🏃♂️ 🏃♂️ 🏃♂️
13 | run: pnpm install --no-frozen-lockfile
14 | linter:
15 | runs-on: ubuntu-20.04
16 | needs: [installation]
17 | concurrency: validation-${{ github.ref }}
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v3
21 | - name: Install dependencies
22 | uses: ./.github/actions/install-deps
23 | - name: Installing packages...🏃♂️ 🏃♂️ 🏃♂️
24 | run: pnpm install --no-frozen-lockfile
25 | - name: Running typecheck... 🩺 🔬 🔭
26 | run: pnpm code:typecheck
27 | - name: Running eslint... 👀
28 | run: pnpm code:lint
29 | test:
30 | runs-on: ubuntu-20.04
31 | needs: [installation]
32 | concurrency: validation-${{ github.ref }}
33 | steps:
34 | - name: Checkout
35 | uses: actions/checkout@v3
36 | - name: Install dependencies
37 | uses: ./.github/actions/install-deps
38 | - name: Installing packages...🏃♂️ 🏃♂️ 🏃♂️
39 | run: pnpm install --no-frozen-lockfile
40 | - name: Running unit test... 🤞 🚑 💊
41 | run: pnpm test:unit --runInBand --coverage
42 | build:
43 | runs-on: ubuntu-20.04
44 | needs: [installation, linter, test]
45 | steps:
46 | - name: Checkout
47 | uses: actions/checkout@v3
48 | - name: Install dependencies
49 | uses: ./.github/actions/install-deps
50 | - name: Installing packages...🏃♂️ 🏃♂️ 🏃♂️
51 | run: pnpm install --no-frozen-lockfile
52 | - name: Run build Chrome
53 | run: pnpm app:chrome
54 | - name: Run build Firefox
55 | run: pnpm app:firefox
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | coverage/
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npm run lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | echo "Running eslint... 👀"
5 | npm run code:lint
6 |
7 | echo "Running unit test... 🤞 🚑 💊"
8 | npm run test:unit -- --coverage
9 |
10 | echo "Running stylelint... 💅 💅 💅"
11 | npm run css:lint
12 |
13 | echo "Running typecheck... 🩺 🔬 🔭"
14 | npm run code:typecheck
15 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18.9.1
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard"
3 | }
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {"recommendations": ["svelte.svelte-vscode"]}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright 2021 davidnguyen179
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
--------------------------------------------------------------------------------
/PRIVACY_POLICY.txt:
--------------------------------------------------------------------------------
1 | Chrome Web Store & Mozilla Add-on Store & Microsoft Edge Add-on Store Privacy Policies.
2 | ======================================================================================================
3 |
4 | Introduction
5 | ============
6 |
7 | Introduce your web extension
8 |
9 | Information We Collect
10 | ======================
11 |
12 | Does your web extension collect data?
13 |
14 | - I collect data
15 | - I don't collect data
16 |
17 | How it uses cookies
18 | ========================
19 |
20 | If your web extension uses cookies, describe where your extension uses cookies and why?
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Extension Svelte Boilerplate
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | This boilerplate using [Svelte](https://svelte.dev/) provides a skeleton to start developing cross-browser web extensions.
10 |
11 |
12 | [](#contributors-)
13 |
14 |
15 | [](https://codecov.io/gh/davidnguyen179/web-extension-svelte-boilerplate)  [](https://github.com/prettier/prettier) [](https://github.com/davidnguyen179/web-extension-svelte-boilerplate/pulls) [](https://github.com/davidnguyen179/web-extension-svelte-boilerplate/blob/main/LICENSE)
16 | ## Development
17 |
18 | ```bash
19 | npm i
20 | ```
21 |
22 | **Chrome**
23 |
24 | ```bash
25 | npm run app:chrome-dev
26 | ```
27 |
28 | **Edge**
29 |
30 | ```bash
31 | npm run app:edge-dev
32 | ```
33 |
34 | **Firefox**
35 |
36 | ```bash
37 | npm run app:firefox-dev
38 | ```
39 |
40 | ## Production
41 |
42 | ```bash
43 | npm i
44 | ```
45 |
46 | ```bash
47 | npm run app:chrome
48 | ```
49 |
50 | **Edge**
51 |
52 | ```bash
53 | npm run app:edge
54 | ```
55 |
56 | **Firefox**
57 |
58 | ```bash
59 | npm run app:firefox
60 | ```
61 |
62 | ## Load package to browsers
63 |
64 | **Chrome**
65 |
66 | 1. Go to the browser's URL address bar
67 | 2. Enter `chrome://extensions/`
68 | 3. Switch to "**Developer mode**"
69 | 4. Load extension by clicking "**Load unpacked**"
70 | 5. Browse to `dist/` in source code
71 | 6. Done!
72 |
73 | Check here for more detail: [https://developer.chrome.com/extensions/getstarted](https://developer.chrome.com/extensions/getstarted)
74 |
75 | **Edge**
76 |
77 | 1. Go to the browser's URL address bar
78 | 2. Enter `edge://extensions/`
79 | 3. Turn on `Developer mode`
80 | 4. Load extension by clicking "**Load unpacked**"
81 | 5. Browse to `dist/` in source code
82 | 6. Done!
83 |
84 | **Firefox**
85 |
86 | 1. Go to the browser's URL address bar
87 | 2. Enter `about:debugging#/runtime/this-firefox`
88 | 3. Click **Load Temporary Add-on...**
89 | 4. Browse to your `manifest.json` & click **Open**
90 | 5. Done!
91 |
92 | Check here for more details: [https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/)
93 |
94 | ## How to keep devtool appears when developing
95 |
96 | Simple, just open chrome extension as a page. To do that, follow below steps:
97 |
98 | 1. Open extension management page
99 |
100 |
101 | 2. Copy extension id
102 |
103 |
104 |
105 | 3. Open extension as a page `chrome-extension:///popup.html`
106 |
107 | - For example: [chrome-extension://npjcjlkchmiidojhockoecphakigbaej/popup.html](chrome-extension://npjcjlkchmiidojhockoecphakigbaej/popup.html)
108 |
109 |
110 |
111 |
112 |
113 | ## Privacy Policy file
114 |
115 | Web stores require you to describe what the extension is or if you collect data of users. You also need to describe why you do that.
116 |
117 | Make sure you include this file when you publish an extension, the web store may ask you to upload it.
118 |
119 | **Note:** If you don't know how to write a privacy policy file, you can look at the [existing one](https://github.com/davidnguyen179/web-extension-boilerplate/blob/main/PRIVACY_POLICY.txt) attached on this repository.
120 |
121 | ## Badges
122 |
123 | You can add badges to your project to indicate the version or to show how many users use your extension here:
124 |
125 | - [https://shields.io/](https://shields.io/)
126 |
127 | ## CI/CD with github action
128 |
129 | You can modify the `.github/workflows` to show the CI/CD flow.
130 |
131 | - [https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-nodejs](https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-nodejs)
132 |
133 | ## How to publish web extension
134 |
135 | | Browsers | Web store |
136 | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
137 | | Chrome | [https://developer.chrome.com/webstore/publish](https://developer.chrome.com/webstore/publish) |
138 | | Microsoft Edge | [https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension) |
139 | | Firefox | [https://extensionworkshop.com/documentation/publish/submitting-an-add-on](https://extensionworkshop.com/documentation/publish/submitting-an-add-on) |
140 |
141 | ## References
142 |
143 | - Develop your first Chrome extension & Chromium Edge: [https://developer.chrome.com/extensions](https://developer.chrome.com/extensions)
144 | - Develop your first Firefox add-on: [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension)
145 | - If you have any Chrome extension issues, you can post it here: [https://groups.google.com/a/chromium.org/forum/#!forum/chromium-extensions](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-extensions)
146 | - [Jack and Amy Dev youtube channel](https://www.youtube.com/channel/UCVj3dGw75v8aHFYD6CL1tFg), which explains some common mistakes made when developing Chrome extensions.
147 |
148 | ## Most important
149 |
150 | Follow me on Twitter! https://twitter.com/davidnguyen1791
151 |
152 | Thanks!
153 |
154 | ## License
155 |
156 | MIT
157 |
158 | ## Contributors ✨
159 |
160 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
161 |
162 |
163 |
164 |
165 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
178 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // babel.config.js
2 | module.exports = {
3 | presets: [
4 | ['@babel/preset-env', { targets: { node: 'current' } }],
5 | '@babel/preset-typescript',
6 | ],
7 | };
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const ignores = ['/node_modules/', '__mocks__'];
2 |
3 | module.exports = {
4 | testMatch: ['**/__test/**/*.spec.+(ts|tsx|js)'],
5 | testPathIgnorePatterns: [...ignores],
6 | coveragePathIgnorePatterns: [...ignores, 'src/(umd|cjs|esm)-entry.js$'],
7 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
8 | coverageDirectory: './coverage',
9 | coverageThreshold: {
10 | global: {
11 | branches: 80,
12 | functions: 100,
13 | lines: 100,
14 | statements: 100,
15 | },
16 | "./src/components/**/*.svelte": { // *.svelte test coverage always results 0 percent for "Branch"
17 | functions: 100,
18 | lines: 100,
19 | statements: 100,
20 | },
21 | },
22 | moduleFileExtensions: ['ts', 'tsx', 'js', 'svelte'],
23 | transform: {
24 | '^.+\\.svelte$': [
25 | "svelte-jester",
26 | {
27 | "preprocess": true
28 | }
29 | ],
30 | '^.+\\.(ts|tsx)$': 'ts-jest',
31 | '^.+\\.js$': 'babel-jest',
32 | },
33 | verbose: true,
34 | setupFilesAfterEnv: ['./jest.setup.js']
35 | };
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | // https://github.com/testing-library/jest-dom
2 | require('@testing-library/jest-dom');
3 | require('whatwg-fetch');
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-extension-svelte-boilerplate",
3 | "version": "1.0.0",
4 | "description": "Web extension Svelte boilerplate",
5 | "scripts": {
6 | "code:lint": "eslint '**/**/*.{ts,tsx,svelte}'",
7 | "code:lint-fix": "eslint --fix",
8 | "code:validate": "svelte-check",
9 | "code:typecheck": "./node_modules/.bin/tsc --noEmit",
10 | "code:prettier": "prettier --write",
11 | "css:lint": "stylelint 'src/assets/**/**/*.css'",
12 | "css:lint-fix": "stylelint --fix",
13 | "app:chrome": "rimraf dist && cross-env BROWSER='chrome' webpack --config webpack/webpack.prod.js",
14 | "app:chrome-dev": "cross-env BROWSER='chrome' webpack --config webpack/webpack.dev.js --watch",
15 | "app:edge": "rimraf dist && cross-env BROWSER='edge' webpack --config webpack/webpack.prod.js",
16 | "app:edge-dev": "cross-env BROWSER='edge' webpack --config webpack/webpack.dev.js --watch",
17 | "app:firefox": "rimraf dist && cross-env BROWSER='firefox' webpack --config webpack/webpack.prod.js",
18 | "app:firefox-dev": "cross-env BROWSER='firefox' webpack --config webpack/webpack.dev.js --watch",
19 | "test:unit": "jest",
20 | "lint-staged": "lint-staged",
21 | "test:unit-update-snapshot": "jest --updateSnapshot"
22 | },
23 | "author": "David Nguyen (dzungnguyen179@gmail.com)",
24 | "devDependencies": {
25 | "@babel/core": "^7.14.2",
26 | "@babel/preset-env": "^7.14.2",
27 | "@babel/preset-typescript": "^7.13.0",
28 | "@testing-library/jest-dom": "^5.12.0",
29 | "@testing-library/svelte": "^3.0.3",
30 | "@tsconfig/svelte": "^1.0.10",
31 | "@types/chrome": "0.0.139",
32 | "@types/jest": "^26.0.23",
33 | "@types/node": "^18.7.19",
34 | "@types/sinon": "^10.0.0",
35 | "@types/testing-library__jest-dom": "^5.9.5",
36 | "@typescript-eslint/eslint-plugin": "^4.23.0",
37 | "@typescript-eslint/parser": "^4.23.0",
38 | "babel-jest": "^26.6.3",
39 | "copy-webpack-plugin": "^8.1.1",
40 | "cross-env": "^7.0.3",
41 | "css-loader": "^5.2.4",
42 | "eslint": "^7.26.0",
43 | "eslint-plugin-import": "^2.23.0",
44 | "eslint-plugin-svelte3": "^3.2.0",
45 | "husky": "^8.0.1",
46 | "jest": "^26.6.3",
47 | "less": "^4.1.1",
48 | "lint-staged": "^13.0.3",
49 | "mini-css-extract-plugin": "^1.6.0",
50 | "prettier": "^2.3.0",
51 | "prettier-plugin-svelte": "^2.3.0",
52 | "rimraf": "^3.0.2",
53 | "sinon": "^10.0.0",
54 | "svelte": "^3.59.2",
55 | "svelte-check": "^1.0.46",
56 | "svelte-jester": "^1.5.0",
57 | "svelte-loader": "^3.0.0",
58 | "svelte-preprocess": "^4.3.0",
59 | "svelte-preprocess-less": "^0.4.0",
60 | "ts-jest": "^26.5.6",
61 | "ts-loader": "^8.2.0",
62 | "tslib": "^2.0.1",
63 | "typescript": "^4.2.4",
64 | "web-ext-types": "^3.2.1",
65 | "webpack": "^5.76.0",
66 | "webpack-cli": "^4.4.0",
67 | "whatwg-fetch": "^3.0.0"
68 | },
69 | "keywords": [
70 | "chrome extension",
71 | "microsoft edge extension",
72 | "firefox addon",
73 | "web extension",
74 | "typescript",
75 | "ts",
76 | "jest",
77 | "sinon"
78 | ],
79 | "repository": {
80 | "type": "git",
81 | "url": "https://github.com/davidnguyen179/web-extension-svelte-boilerplate.git"
82 | },
83 | "license": "MIT",
84 | "bugs": {
85 | "url": "https://github.com/davidnguyen179/web-extension-svelte-boilerplate/issues"
86 | },
87 | "homepage": "https://github.com/davidnguyen179/web-extension-svelte-boilerplate/blob/main/README.md",
88 | "funding": {
89 | "type": "individual",
90 | "url": "https://paypal.me/davidnguyen179"
91 | },
92 | "lint-staged": {
93 | "*.{js,jsx,ts,tsx,css,svelte}": [
94 | "npm run code:lint-fix",
95 | "npm run css:lint-fix",
96 | "npm run code:prettier"
97 | ]
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/public/chrome_manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "The Clock",
3 | "version": "1.0",
4 | "description": "The clock which shows time and date quickly. It has background images correspond to the periods of a day.",
5 | "permissions": [
6 | "declarativeContent"
7 | ],
8 | "manifest_version": 3,
9 | "background": {
10 | "service_worker": "background.js"
11 | },
12 | "action": {
13 | "default_popup": "popup.html",
14 | "default_icon": {
15 | "16": "images/logo/date-time-16.png",
16 | "32": "images/logo/date-time-32.png",
17 | "48": "images/logo/date-time-48.png",
18 | "128": "images/logo/date-time-128.png"
19 | }
20 | },
21 | "icons": {
22 | "16": "images/logo/date-time-16.png",
23 | "32": "images/logo/date-time-32.png",
24 | "48": "images/logo/date-time-48.png",
25 | "128": "images/logo/date-time-128.png"
26 | }
27 | }
--------------------------------------------------------------------------------
/public/edge_manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "The Clock",
3 | "version": "1.0",
4 | "description": "The clock which shows time and date quickly. It has background images correspond to the periods of a day.",
5 | "permissions": [
6 | "declarativeContent"
7 | ],
8 | "manifest_version": 2,
9 | "background": {
10 | "content_scripts": "background.js"
11 | },
12 | "browser_action": {
13 | "default_popup": "popup.html",
14 | "default_icon": {
15 | "16": "images/logo/date-time-16.png",
16 | "32": "images/logo/date-time-32.png",
17 | "48": "images/logo/date-time-48.png",
18 | "128": "images/logo/date-time-128.png"
19 | }
20 | },
21 | "icons": {
22 | "16": "images/logo/date-time-16.png",
23 | "32": "images/logo/date-time-32.png",
24 | "48": "images/logo/date-time-48.png",
25 | "128": "images/logo/date-time-128.png"
26 | }
27 | }
--------------------------------------------------------------------------------
/public/firefox_manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "The Clock",
3 | "version": "1.0",
4 | "description": "The clock which shows time and date quickly. It has background images correspond to the periods of a day.",
5 | "permissions": [],
6 | "manifest_version": 2,
7 | "background": {
8 | "scripts": [
9 | "background.js"
10 | ],
11 | "persistent": true
12 | },
13 | "browser_action": {
14 | "default_popup": "popup.html",
15 | "default_icon": {
16 | "16": "images/logo/date-time-16.png",
17 | "32": "images/logo/date-time-32.png",
18 | "48": "images/logo/date-time-48.png",
19 | "128": "images/logo/date-time-128.png"
20 | },
21 | "default_title": "Getting Started Example"
22 | },
23 | "icons": {
24 | "16": "images/logo/date-time-16.png",
25 | "32": "images/logo/date-time-32.png",
26 | "48": "images/logo/date-time-48.png",
27 | "128": "images/logo/date-time-128.png"
28 | }
29 | }
--------------------------------------------------------------------------------
/public/images/afternoon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/afternoon.png
--------------------------------------------------------------------------------
/public/images/logo/date-time-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/logo/date-time-128.png
--------------------------------------------------------------------------------
/public/images/logo/date-time-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/logo/date-time-16.png
--------------------------------------------------------------------------------
/public/images/logo/date-time-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/logo/date-time-32.png
--------------------------------------------------------------------------------
/public/images/logo/date-time-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/logo/date-time-48.png
--------------------------------------------------------------------------------
/public/images/morning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/morning.png
--------------------------------------------------------------------------------
/public/images/night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidnguyen11/web-extension-svelte-boilerplate/136d6b5d5de5e6fe5865e2ae87c9562c1f39f6fc/public/images/night.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Datetime web extension
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/css/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/src/background/chrome/background.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | chrome.runtime.onInstalled.addListener(function () {
4 | // Make extension work on all pages
5 | chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
6 | chrome.declarativeContent.onPageChanged.addRules([
7 | {
8 | conditions: [new chrome.declarativeContent.PageStateMatcher({})],
9 | actions: [new chrome.declarativeContent.ShowPageAction()],
10 | },
11 | ]);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/background/edge/background.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | chrome.runtime.onInstalled.addListener(function () {
4 | // Make extension work on all pages
5 | chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
6 | chrome.declarativeContent.onPageChanged.addRules([
7 | {
8 | conditions: [new chrome.declarativeContent.PageStateMatcher({})],
9 | actions: [new chrome.declarativeContent.ShowPageAction()],
10 | },
11 | ]);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/background/firefox/background.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/components/App/App.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
--------------------------------------------------------------------------------
/src/components/App/__test/App.spec.ts:
--------------------------------------------------------------------------------
1 | import App from '../App.svelte';
2 | import { render } from '@testing-library/svelte';
3 |
4 | describe('testing getDisplayDateTime', () => {
5 | it('should display datetime', async () => {
6 | const AppRendered = render(App, {
7 | date: 'Fri May 14',
8 | time: '16:44',
9 | period: 'afternoon',
10 | });
11 |
12 | expect(AppRendered.container).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/App/__test/__snapshots__/App.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`testing getDisplayDateTime should display datetime 1`] = `
4 |
5 |
6 |
11 |
14 |
18 | Fri May 14
19 |
20 |
21 |
25 | 16:44
26 |
27 |
28 |
29 |
30 |
31 | `;
32 |
--------------------------------------------------------------------------------
/src/components/AppWrapper/AppWrapper.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
24 |
--------------------------------------------------------------------------------
/src/components/AppWrapper/__test/AppWrapper.spec.ts:
--------------------------------------------------------------------------------
1 | import AppWrapper from '../AppWrapper.svelte';
2 | import { render } from '@testing-library/svelte';
3 |
4 | describe('testing app wrapper', () => {
5 | it('should render background image', async () => {
6 | const { getByRole } = render(AppWrapper, {
7 | period: 'afternoon',
8 | });
9 |
10 | const wrapper = getByRole('main');
11 | expect(wrapper).toHaveStyle('background-image: url(images/afternoon.png)');
12 | });
13 |
14 | it('it should render background image default without period', async () => {
15 | const { getByRole } = render(AppWrapper);
16 |
17 | const wrapper = getByRole('main');
18 | expect(wrapper).toHaveStyle('background-image: url(images/morning.png)');
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/components/DateTime/DateTime.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {date}
14 |
15 |
16 | {time}
17 |
18 |
19 |
30 |
--------------------------------------------------------------------------------
/src/components/DateTime/__test/DateTime.ts:
--------------------------------------------------------------------------------
1 | import DateTime from '../DateTime.svelte';
2 | import { render } from '@testing-library/svelte';
3 |
4 | describe('testing datetime', () => {
5 | it('should render datetime', async () => {
6 | const { getAllByRole } = render(DateTime, {
7 | date: 'Fri May 14',
8 | time: '16:44',
9 | period: 'afternoon',
10 | });
11 |
12 | const [h2, h3] = getAllByRole('heading');
13 |
14 | expect(h2).toHaveTextContent('Fri May 14');
15 | expect(h3).toHaveTextContent('16:44');
16 | });
17 |
18 | it('it should render datetime without period', async () => {
19 | const { getAllByRole } = render(DateTime, {
20 | date: 'Fri May 14',
21 | time: '16:44',
22 | });
23 |
24 | const [h2, h3] = getAllByRole('heading');
25 |
26 | expect(h2).toHaveTextContent('Fri May 14');
27 | expect(h3).toHaveTextContent('16:44');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/lib/__test/get-display-datetime.spec.ts:
--------------------------------------------------------------------------------
1 | import sinon from 'sinon';
2 | import { getDisplayDateTime, getPeriod } from '../get-display-datetime';
3 |
4 | describe('testing getDisplayDateTime', () => {
5 | let sandboxes;
6 |
7 | beforeEach(() => {
8 | sandboxes = sinon.createSandbox();
9 | sandboxes.useFakeTimers({
10 | now: new Date(2020, 10, 19, 23, 0),
11 | });
12 | });
13 |
14 | afterEach(() => {
15 | sandboxes.restore();
16 | });
17 |
18 | describe('testing getDisplayDateTime', () => {
19 | it('should return Thu Nov 19 23:00', () => {
20 | const actual = getDisplayDateTime();
21 | const expected = { date: 'Thu Nov 19', time: '23:00', period: 'night' };
22 | expect(actual).toEqual(expected);
23 | });
24 |
25 | it('should return Thu Nov 19 01:00', () => {
26 | sinon.useFakeTimers({
27 | now: new Date(2020, 10, 19, 1, 0),
28 | });
29 | const actual = getDisplayDateTime();
30 | const expected = { date: 'Thu Nov 19', time: '01:00', period: 'night' };
31 | expect(actual).toEqual(expected);
32 | });
33 |
34 | it('should return Thu Nov 19 23:23', () => {
35 | sinon.useFakeTimers({
36 | now: new Date(2020, 10, 19, 23, 23),
37 | });
38 | const actual = getDisplayDateTime();
39 | const expected = { date: 'Thu Nov 19', time: '23:23', period: 'night' };
40 | expect(actual).toEqual(expected);
41 | });
42 | });
43 |
44 | describe('testing getPeriod', () => {
45 | it('should return morning - 05:00 AM', () => {
46 | const actual = getPeriod(new Date(2020, 10, 19, 5, 0));
47 | const expected = 'morning';
48 | expect(actual).toEqual(expected);
49 | });
50 |
51 | it('should return morning - 11:59 AM', () => {
52 | const actual = getPeriod(new Date(2020, 10, 19, 11, 59));
53 | const expected = 'morning';
54 | expect(actual).toEqual(expected);
55 | });
56 |
57 | it('should return afternoon - 12:00 PM', () => {
58 | const actual = getPeriod(new Date(2020, 10, 19, 12, 0));
59 | const expected = 'afternoon';
60 | expect(actual).toEqual(expected);
61 | });
62 |
63 | it('should return afternoon - 04:59 PM', () => {
64 | const actual = getPeriod(new Date(2020, 10, 19, 16, 59));
65 | const expected = 'afternoon';
66 | expect(actual).toEqual(expected);
67 | });
68 |
69 | it('should return night - 05:00 PM', () => {
70 | const actual = getPeriod(new Date(2020, 10, 19, 17, 0));
71 | const expected = 'night';
72 | expect(actual).toEqual(expected);
73 | });
74 |
75 | it('should return night - 04:59 AM', () => {
76 | const actual = getPeriod(new Date(2020, 10, 19, 4, 59));
77 | const expected = 'night';
78 | expect(actual).toEqual(expected);
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/src/lib/__test/get-palette.spec.ts:
--------------------------------------------------------------------------------
1 | import { getPalette } from '../get-palette';
2 |
3 | describe('testing getPalette', () => {
4 | it('should return morning color code - #282e54', () => {
5 | const actual = getPalette('morning');
6 | const expected = '#282e54';
7 | expect(actual).toEqual(expected);
8 | });
9 |
10 | it('should return afternoon color code - #000000', () => {
11 | const actual = getPalette('afternoon');
12 | const expected = '#000000';
13 | expect(actual).toEqual(expected);
14 | });
15 |
16 | it('should return morning color code - #ffdd91', () => {
17 | const actual = getPalette('night');
18 | const expected = '#ffdd91';
19 | expect(actual).toEqual(expected);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/lib/get-display-datetime.ts:
--------------------------------------------------------------------------------
1 | import type { DayPeriod } from '../type/index.type';
2 |
3 | export function getDisplayDateTime() {
4 | const day = getDay();
5 | const month = getMonth();
6 | const d = new Date();
7 | const date = d.getDate();
8 | let hours = d.getHours().toString();
9 | let min = d.getMinutes().toString();
10 |
11 | if (hours.length < 2) {
12 | hours = `0${hours}`;
13 | }
14 |
15 | if (min.length < 2) {
16 | min = `0${min}`;
17 | }
18 |
19 | return {
20 | date: `${day} ${month} ${date}`,
21 | time: `${hours}:${min}`,
22 | period: getPeriod(d),
23 | };
24 | }
25 |
26 | export function getMonth() {
27 | const date = new Date();
28 | const months = [
29 | 'January',
30 | 'February',
31 | 'March',
32 | 'April',
33 | 'May',
34 | 'June',
35 | 'July',
36 | 'August',
37 | 'September',
38 | 'October',
39 | 'November',
40 | 'December',
41 | ];
42 | return months[date.getMonth()].substring(0, 3);
43 | }
44 |
45 | export function getDay() {
46 | const date = new Date();
47 | const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
48 | return days[date.getDay()].substring(0, 3);
49 | }
50 |
51 | export function getPeriod(date: Date): DayPeriod {
52 | const hours = date.getHours();
53 |
54 | // 5:00 AM — 11:59 AM => morning
55 | if (hours >= 5 && hours < 12) {
56 | return 'morning';
57 | }
58 |
59 | // 12:00 PM — 4:59 PM => afternoon
60 | if (hours >= 12 && hours < 17) {
61 | return 'afternoon';
62 | }
63 |
64 | // 5:00 PM — 4:59 AM => night
65 | return 'night';
66 | }
67 |
--------------------------------------------------------------------------------
/src/lib/get-palette.ts:
--------------------------------------------------------------------------------
1 | import type { DayPeriod } from '../type/index.type';
2 |
3 | export function getPalette(period: DayPeriod) {
4 | return {
5 | morning: '#282e54',
6 | afternoon: '#000000',
7 | night: '#ffdd91',
8 | }[period];
9 | }
10 |
--------------------------------------------------------------------------------
/src/popup.ts:
--------------------------------------------------------------------------------
1 | import './assets/css/global.css';
2 |
3 | import App from './components/App/App.svelte';
4 | import { getDisplayDateTime } from './lib/get-display-datetime';
5 |
6 | const { date, time, period } = getDisplayDateTime();
7 |
8 | const app = new App({
9 | target: document.body,
10 | props: {
11 | date,
12 | time,
13 | period,
14 | },
15 | });
16 |
17 | export default app;
18 |
--------------------------------------------------------------------------------
/src/type/index.type.ts:
--------------------------------------------------------------------------------
1 | export type DayPeriod = 'morning' | 'afternoon' | 'night';
2 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | const sveltePreprocess = require("svelte-preprocess");
2 |
3 | module.exports = {
4 | preprocess: sveltePreprocess(),
5 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/svelte/tsconfig.json",
3 | "include": [
4 | "src/**/*",
5 | "src/node_modules/**/*",
6 | "./jest-setup.js"
7 | ],
8 | "exclude": [
9 | "node_modules/*",
10 | ],
11 | "compilerOptions": {
12 | "rootDir": "src",
13 | "isolatedModules": false,
14 | "typeRoots": [
15 | "node_modules/@types",
16 | ],
17 | "types": [
18 | "jest",
19 | "node",
20 | "svelte",
21 | "@testing-library/jest-dom"
22 | ]
23 | },
24 | }
--------------------------------------------------------------------------------
/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2 | const path = require('path');
3 | const CopyPlugin = require('copy-webpack-plugin');
4 |
5 | const browser = process.env.BROWSER;
6 | const BUILD_DIR_NAME = 'dist';
7 | const SRC_DIR_NAME = 'src';
8 |
9 | module.exports = {
10 | entry: {
11 | popup: path.join(__dirname, `../${SRC_DIR_NAME}/popup.ts`),
12 | background: path.join(__dirname, `../${SRC_DIR_NAME}/background/${browser}/background.ts`),
13 | },
14 | output: {
15 | path: path.join(__dirname, `../${BUILD_DIR_NAME}`),
16 | filename: '[name].js',
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.tsx?$/,
22 | use: 'ts-loader',
23 | exclude: /node_modules/,
24 | },
25 | {
26 | test: /\.css$/,
27 | use: [
28 | MiniCssExtractPlugin.loader,
29 | 'css-loader'
30 | ]
31 | },
32 | {
33 | // required to prevent errors from Svelte on Webpack 5+
34 | test: /node_modules\/svelte\/.*\.mjs$/,
35 | resolve: {
36 | fullySpecified: false
37 | }
38 | }
39 | ],
40 | },
41 | resolve: {
42 | alias: {
43 | svelte: path.dirname(require.resolve('svelte/package.json'))
44 | },
45 | extensions: ['.mjs', '.js', '.ts', '.svelte'],
46 | mainFields: ['svelte', 'browser', 'module', 'main'],
47 | conditionNames: ['svelte', 'require', 'node'],
48 | },
49 | plugins: [
50 | new MiniCssExtractPlugin({
51 | filename: '[name].css'
52 | }),
53 | new CopyPlugin({
54 | patterns: [
55 | { from: './images', to: `../${BUILD_DIR_NAME}/images`, context: 'public' },
56 | { from: './popup.html', to: `../${BUILD_DIR_NAME}/popup.html`, context: 'public' },
57 | { from: `${browser}_manifest.json`, to: `../${BUILD_DIR_NAME}/manifest.json`, context: 'public' },
58 | ],
59 | }),
60 | ],
61 | };
62 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const { merge } = require('webpack-merge');
2 | const common = require('./webpack.common.js');
3 | const sveltePreprocess = require('svelte-preprocess');
4 |
5 | module.exports = merge(common, {
6 | devtool: 'inline-source-map',
7 | mode: 'development',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.svelte$/,
12 | use: {
13 | loader: 'svelte-loader',
14 | options: {
15 | compilerOptions: {
16 | dev: true
17 | },
18 | emitCss: false,
19 | hotReload: true,
20 | preprocess: sveltePreprocess({ sourceMap: true })
21 | }
22 | }
23 | },
24 | ],
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const { merge } = require('webpack-merge');
2 | const TerserPlugin = require('terser-webpack-plugin');
3 | const common = require('./webpack.common.js');
4 | const sveltePreprocess = require('svelte-preprocess');
5 |
6 | module.exports = merge(common, {
7 | mode: 'production',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.svelte$/,
12 | use: {
13 | loader: 'svelte-loader',
14 | options: {
15 | compilerOptions: {
16 | dev: false
17 | },
18 | emitCss: true,
19 | hotReload: false,
20 | preprocess: sveltePreprocess({ sourceMap: false })
21 | }
22 | }
23 | },
24 | ],
25 | },
26 | optimization: {
27 | minimize: true,
28 | minimizer: [
29 | new TerserPlugin({
30 | /*
31 | * Google requires some conditions:
32 | * - Removal of whitespace, newlines, code comments, and block delimiters
33 | * - Shortening of variable and function names
34 | * - Collapsing the number of JavaScript files
35 | */
36 | terserOptions: {
37 | compress: true, // To rename variables & function names
38 | mangle: true, // Note `mangle.properties` is `false` by default.
39 | },
40 | }),
41 | ],
42 | },
43 | });
44 |
--------------------------------------------------------------------------------