├── .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 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 13 | 14 | 15 | [![codecov](https://codecov.io/gh/davidnguyen179/web-extension-svelte-boilerplate/branch/main/graph/badge.svg?token=IKX2LE11LE)](https://codecov.io/gh/davidnguyen179/web-extension-svelte-boilerplate) ![ci/cd](https://github.com/davidnguyen179/web-extension-svelte-boilerplate/workflows/ci/cd/badge.svg) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/davidnguyen179/web-extension-svelte-boilerplate/pulls) [![MIT license](https://img.shields.io/github/license/davidnguyen179/web-extension-svelte-boilerplate)](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 | Screen Shot 2021-05-27 at 14 44 56 100 | 101 | 2. Copy extension id 102 | 103 | Screen Shot 2021-05-27 at 14 45 20 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 | chrome-extension://npjcjlkchmiidojhockoecphakigbaej/popup.html 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 | 166 | 167 | 168 | 169 | 170 |

David Nguyen

💻 📖 🎨 🤔 🚇 🚧 👀 ⚠️

DK

💻 🤔 🚇 🚧 👀 ⚠️
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 | --------------------------------------------------------------------------------