├── .all-contributorsrc ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pa11yci ├── .prettierrc ├── .stylelintrc ├── .travis.yml ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── now.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── renovate.json ├── src ├── index.html ├── js │ └── app.js ├── static │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── og.jpg │ ├── safari-pinned-tab.svg │ └── site.webmanifest └── style │ └── main.scss ├── tailwind.config.js ├── webpack.config.common.js ├── webpack.config.dev.js └── webpack.config.prod.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "danestves", 10 | "name": "Daniel Esteves", 11 | "avatar_url": "https://avatars0.githubusercontent.com/u/31737273?v=4", 12 | "profile": "https://danestves.com/", 13 | "contributions": [ 14 | "code" 15 | ] 16 | }, 17 | { 18 | "login": "johannschopplich", 19 | "name": "Johann Schopplich", 20 | "avatar_url": "https://avatars2.githubusercontent.com/u/27850750?v=4", 21 | "profile": "https://johannschopplich.com", 22 | "contributions": [ 23 | "code", 24 | "translation" 25 | ] 26 | } 27 | ], 28 | "contributorsPerLine": 7, 29 | "projectName": "invoicegenerator", 30 | "projectOwner": "danestves", 31 | "repoType": "github", 32 | "repoHost": "https://github.com", 33 | "skipCi": true 34 | } 35 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["html"], 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "rules": { 9 | "arrow-body-style": "off", 10 | "arrow-parens": "off", 11 | "comma-dangle": "off", 12 | "consistent-return": "off", 13 | "default-case": "off", 14 | "function-paren-newline": "off", 15 | "import/extensions": "off", 16 | "import/no-cycle": "off", 17 | "import/no-extraneous-dependencies": "off", 18 | "import/no-named-as-default-member": "off", 19 | "import/no-named-as-default": "off", 20 | "import/no-unresolved": "off", 21 | "import/order": "off", 22 | "import/prefer-default-export": "off", 23 | "linebreak-style": "off", 24 | "new-cap": "off", 25 | "no-confusing-arrow": "off", 26 | "no-else-return": "off", 27 | "no-loop-func": "off", 28 | "no-mixed-operators": "off", 29 | "no-multi-assign": "off", 30 | "no-nested-ternary": "off", 31 | "no-new": "off", 32 | "no-param-reassign": "off", 33 | "no-prototype-builtins": "off", 34 | "no-restricted-globals": "off", 35 | "no-restricted-properties": "off", 36 | "no-return-assign": "off", 37 | "no-shadow": "off", 38 | "no-underscore-dangle": "off", 39 | "no-use-before-define": ["error", { "functions": false }], 40 | "object-curly-newline": "off", 41 | "operator-linebreak": "off", 42 | "radix": "off", 43 | "semi": ["error", "never"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please ensure your pull request adheres to the following guidelines: 2 | 3 | - [ ] Use the following format: `* [owner/repo](link)` 4 | - [ ] Link additions should be added to the bottom of the relevant category. 5 | - [ ] New categories or improvements to the existing categorization are welcome. 6 | - [ ] Search previous suggestions before making a new one, as yours may be a duplicate. 7 | - [ ] Sort by alphabetical order 8 | 9 | Thanks for contributing! 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows 3 | # Edit at https://www.gitignore.io/?templates=node,linux,macos,windows 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### Node ### 49 | # Logs 50 | logs 51 | *.log 52 | npm-debug.log* 53 | yarn-debug.log* 54 | yarn-error.log* 55 | lerna-debug.log* 56 | 57 | # Diagnostic reports (https://nodejs.org/api/report.html) 58 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 59 | 60 | # Runtime data 61 | pids 62 | *.pid 63 | *.seed 64 | *.pid.lock 65 | 66 | # Directory for instrumented libs generated by jscoverage/JSCover 67 | lib-cov 68 | 69 | # Coverage directory used by tools like istanbul 70 | coverage 71 | *.lcov 72 | 73 | # nyc test coverage 74 | .nyc_output 75 | 76 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 77 | .grunt 78 | 79 | # Bower dependency directory (https://bower.io/) 80 | bower_components 81 | 82 | # node-waf configuration 83 | .lock-wscript 84 | 85 | # Compiled binary addons (https://nodejs.org/api/addons.html) 86 | build/Release 87 | 88 | # Dependency directories 89 | node_modules/ 90 | jspm_packages/ 91 | 92 | # TypeScript v1 declaration files 93 | typings/ 94 | 95 | # TypeScript cache 96 | *.tsbuildinfo 97 | 98 | # Optional npm cache directory 99 | .npm 100 | 101 | # Optional eslint cache 102 | .eslintcache 103 | 104 | # Optional REPL history 105 | .node_repl_history 106 | 107 | # Output of 'npm pack' 108 | *.tgz 109 | 110 | # Yarn Integrity file 111 | .yarn-integrity 112 | 113 | # dotenv environment variables file 114 | .env 115 | .env.test 116 | 117 | # parcel-bundler cache (https://parceljs.org/) 118 | .cache 119 | 120 | # next.js build output 121 | .next 122 | 123 | # nuxt.js build output 124 | .nuxt 125 | 126 | # rollup.js default build output 127 | dist/ 128 | 129 | # Uncomment the public line if your project uses Gatsby 130 | # https://nextjs.org/blog/next-9-1#public-directory-support 131 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 132 | # public 133 | 134 | # Storybook build outputs 135 | .out 136 | .storybook-out 137 | 138 | # vuepress build output 139 | .vuepress/dist 140 | 141 | # Serverless directories 142 | .serverless/ 143 | 144 | # FuseBox cache 145 | .fusebox/ 146 | 147 | # DynamoDB Local files 148 | .dynamodb/ 149 | 150 | # Temporary folders 151 | tmp/ 152 | temp/ 153 | 154 | ### Windows ### 155 | # Windows thumbnail cache files 156 | Thumbs.db 157 | Thumbs.db:encryptable 158 | ehthumbs.db 159 | ehthumbs_vista.db 160 | 161 | # Dump file 162 | *.stackdump 163 | 164 | # Folder config file 165 | [Dd]esktop.ini 166 | 167 | # Recycle Bin used on file shares 168 | $RECYCLE.BIN/ 169 | 170 | # Windows Installer files 171 | *.cab 172 | *.msi 173 | *.msix 174 | *.msm 175 | *.msp 176 | 177 | # Windows shortcuts 178 | *.lnk 179 | 180 | # End of https://www.gitignore.io/api/node,linux,macos,windows 181 | 182 | .now -------------------------------------------------------------------------------- /.pa11yci: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "chromeLaunchConfig": { 4 | "args": ["--no-sandbox"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "jsxSingleQuote": false, 5 | "printWidth": 80, 6 | "quoteProps": "as-needed", 7 | "semi": false, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "none", 11 | "useTabs": false 12 | } 13 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "at-rule-no-unknown": [ true, { 5 | "ignoreAtRules": [ 6 | "extends", 7 | "tailwind" 8 | ] 9 | }], 10 | "block-no-empty": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - lts/* 5 | install: 6 | - npm install 7 | before_script: 8 | - npm rebuild node-sass 9 | script: 10 | - npm run lint:styles 11 | - npm run lint:js 12 | - npm run lint:html 13 | - npm run build 14 | after_script: 15 | - NOW_ALIAS=invoicegenerator node_modules/.bin/now-travis 16 | branches: 17 | only: 18 | - master 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at estevesd8@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [https://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: https://contributor-covenant.org 74 | [version]: https://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at estevesd8@gmail.com. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Esteves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #
Invoice Generator ✨
2 | 3 | 4 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 5 | 6 | 7 | [![Build Status](https://travis-ci.org/danestves/invoicegenerator.png?branch=master)](https://travis-ci.org/danestves/invoicegenerator) [![GitHub issues](https://img.shields.io/github/issues/danestves/invoicegenerator)](https://github.com/danestves/invoicegenerator/issues) [![GitHub forks](https://img.shields.io/github/forks/danestves/invoicegenerator)](https://github.com/danestves/invoicegenerator/network) [![GitHub stars](https://img.shields.io/github/stars/danestves/invoicegenerator)](https://github.com/danestves/invoicegenerator/stargazers) [![GitHub license](https://img.shields.io/github/license/danestves/invoicegenerator)](https://github.com/danestves/invoicegenerator/blob/master/LICENSE) 8 | 9 | This open source project is made to show my knowledge about JavaScript and Frontend Development 🤓 10 | 11 | You can see the live project [here](https://invoicegenerator.now.sh) 🚀 12 | 13 | ## 🚀 Quick start 14 | 15 | 1. **Install dependencies.** 16 | 17 | ```shell 18 | cd invoicegenerator/ 19 | 20 | npm i 21 | ``` 22 | 23 | 2. **Using commands.** 24 | 25 | ```shell 26 | npm run start:dev # Start development server 27 | 28 | npm run start # Start production server 29 | 30 | npm run lint:js # Linter for JavaScript 31 | 32 | npm run lint:styles # Linter for styles 33 | 34 | npm run lint:html # Linter for HTML includir a11y 35 | 36 | npm run build # Build the project 37 | ``` 38 | 39 | ## What we use? 🧐 40 | 41 | 🖥 For the CSS i use [TailwindCSS](https://tailwindcss.com), i extremely recommend you to use this utility framework; it's going to help you to optimize your development time like 10x more faster 42 | 43 | 🕹 For the animation of the dropdown i use [AlpineJS](https://github.com/alpinejs/alpine) that is a reactive and declarative library with the nature of big frameworks like Vue or React at a much lower cost 44 | 45 | 🇺🇸 For the internationalization i use [i18next](https://www.i18next.com/) that is a **framework** written in and for JavaScript to use different languages in your code 46 | 47 | 💭 For the tooltips i use [Tippy.js](https://atomiks.github.io/tippyjs/) that is a lightweight library to make tooltips and popovers more faster than you think 48 | 49 | **Let's go and help open source projects** 😎 50 | 51 | ## Contributors ✨ 52 | 53 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |

Daniel Esteves

💻

Johann Schopplich

💻 🌍
64 | 65 | 66 | 67 | 68 | 69 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 70 | 71 | Please note that this project is released with a [Contributor Code of Conduct](CODE-OF-CONDUCT.md). By participating in this project you agree to abide by its terms. 72 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "alias": "invoicegenerator", 4 | "rewrites": [{ "source": "/(.*)", "destination": "/" }] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "invoicegenerator", 3 | "version": "1.0.2", 4 | "description": "Invoice generator made by @danestves with AlpineJS and TailwindCSS", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.prod.js", 8 | "lint:js": "./node_modules/.bin/eslint src/js/*.js", 9 | "lint:styles": "stylelint \"src/**/*.scss\"", 10 | "lint:html": "pa11y-ci ./src/**/*.html", 11 | "start:dev": "webpack-dev-server --config webpack.config.dev.js -d", 12 | "start": "webpack --config webpack.config.prod.js && http-server ./dist -o" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/danestves/invoicegenerator.git" 17 | }, 18 | "author": "Daniel Esteves ", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@babel/core": "^7.9.0", 22 | "@babel/plugin-transform-runtime": "^7.9.0", 23 | "@babel/preset-env": "^7.9.0", 24 | "@danestves/tailwindcss-darkmode": "^1.1.5", 25 | "@fullhuman/postcss-purgecss": "^2.1.0", 26 | "autoprefixer": "^9.7.5", 27 | "babel-eslint": "^10.1.0", 28 | "babel-loader": "^8.1.0", 29 | "clean-webpack-plugin": "^3.0.0", 30 | "copy-webpack-plugin": "^5.1.1", 31 | "css-loader": "^3.4.2", 32 | "cssnano": "^4.1.10", 33 | "eslint": "^6.8.0", 34 | "eslint-plugin-html": "^6.0.0", 35 | "eslint-plugin-import": "^2.20.2", 36 | "file-loader": "^6.0.0", 37 | "glob": "^7.1.6", 38 | "html-webpack-plugin": "^4.0.4", 39 | "http-server": "^0.12.1", 40 | "mini-css-extract-plugin": "^0.9.0", 41 | "node-sass": "^4.13.1", 42 | "now-travis": "^1.2.0", 43 | "optimize-css-assets-webpack-plugin": "^5.0.3", 44 | "pa11y-ci": "^2.3.0", 45 | "postcss-loader": "^3.0.0", 46 | "raw-loader": "^4.0.0", 47 | "sass-loader": "^8.0.2", 48 | "style-loader": "^1.1.3", 49 | "stylelint": "^13.3.0", 50 | "stylelint-config-standard": "^20.0.0", 51 | "webpack": "^4.42.1", 52 | "webpack-cli": "^3.3.11", 53 | "webpack-dev-server": "^3.10.3", 54 | "webpack-merge": "^4.2.2" 55 | }, 56 | "browserslist": [ 57 | "> 1%", 58 | "last 2 versions" 59 | ], 60 | "dependencies": { 61 | "@babel/runtime": "^7.9.2", 62 | "i18next": "^19.3.4", 63 | "i18next-browser-languagedetector": "^4.0.2", 64 | "postcss-import": "^12.0.1", 65 | "tailwindcss": "^1.2.0", 66 | "tippy.js": "^6.1.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // postcss.config.js 2 | const purgecss = require('@fullhuman/postcss-purgecss')({ 3 | // Specify the paths to all of the template files in your project 4 | content: ['./src/**/*.html'], 5 | 6 | // Include any special characters you're using in this regular expression 7 | defaultExtractor: content => content.match(/[\w-/:]+(? 2 | 3 | 4 | 5 | 6 | 7 | Invoice Generator | Made with AlpineJS and TailwindCSS 8 | 9 | 10 | 11 | 16 | 22 | 28 | 29 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | 47 | 51 | 52 | 53 | 57 | 61 | 65 | 66 | 67 | 68 | 72 | 76 | 77 | 78 | 82 | 83 | 84 | 88 | 92 | 96 | 97 | 101 | 102 | 103 | 104 | 105 | 109 | 110 | 111 |
114 |
117 |
122 |
123 | 131 |
132 |
142 |
143 | 150 | 157 | 164 |
165 |
166 |
167 |
168 |
169 | 170 |
171 |
177 |
178 |

182 | ... 183 |

184 | 185 |
186 |
187 |
192 | 193 | 198 | 199 |
200 |
201 | 202 |
203 |
208 | 209 | 214 | 215 |
216 |
217 |
218 |
219 | 220 |
221 |
222 |
223 | 227 | 228 |
229 | 236 |
237 |
238 | 239 |
240 | 244 | 245 |
246 | 257 |
258 |
259 | 260 |
261 | 265 | 266 |
267 | 278 |
279 |
280 |
281 |
282 |
285 | Placeholder logo image invoice 291 | 292 |
296 | 310 |
311 |
312 | 331 |
332 |
333 | 334 |
335 |
336 | 340 | 347 | 354 | 361 |
362 |
363 | 367 | 374 | 375 | 382 | 383 | 390 |
391 |
392 | 393 |
394 |
395 |

399 |
400 | 401 |
402 |

406 |
407 | 408 |
409 |

410 | 414 | (Incl. GST) 415 |

416 |
417 | 418 |
419 |

420 | 424 | (Incl. GST) 425 |

426 |
427 | 428 |
429 |
430 | 431 | 467 | 468 | 474 | 475 |
476 |
477 |
Total incl. GST
478 |
479 |
480 |
481 |
482 |
483 |
484 | GST(18%) incl. Total 485 |
486 |
487 |
488 |
489 |
490 | 491 |
492 |
493 |
497 | Amount due 498 |
499 |
500 |
504 |
505 |
506 |
507 |
508 | 509 |
510 |

511 | Created by 512 | 518 | @danestves . Built with 520 | 526 | TailwindCSS 527 | 528 | and 529 | 535 | AlpineJS . SVG icons from 537 | 543 | Heroicons. 545 |

546 | 547 |

548 | See project on 549 | 555 | GitHub 556 | 557 |

558 |
559 | 560 | 561 | 721 | 722 | 723 | 724 |
729 |
732 |
736 | 741 | 744 | 745 |
746 | 747 |
750 |

754 | ... 755 |

756 | 757 |
758 | 762 | 768 |
769 | 770 |
771 |
772 | 776 | 782 |
783 | 784 |
785 | 789 | 795 |
796 | 797 |
798 | 802 | 808 |
809 |
810 | 811 |
812 |
813 | 817 | 827 |
830 | 835 | 838 | 839 |
840 |
841 |
842 | 843 |
844 | 851 | 858 |
859 |
860 |
861 |
862 | 863 |
864 | 865 | 866 | 870 | 871 | 1071 | 1072 | 1073 | -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import tippy from 'tippy.js' 3 | import 'tippy.js/dist/tippy.css' 4 | import 'tippy.js/animations/scale.css' 5 | import i18next from 'i18next' 6 | import LanguageDetector from 'i18next-browser-languagedetector' 7 | 8 | /** 9 | * Init tippy 10 | */ 11 | tippy('[data-tippy-content]', { 12 | animation: 'scale' 13 | }) 14 | 15 | /** 16 | * Hot module 17 | */ 18 | if (module.hot) { 19 | module.hot.accept() 20 | } 21 | 22 | /** 23 | * Language switcher 24 | */ 25 | i18next.use(LanguageDetector).init( 26 | { 27 | fallbackLng: 'en', 28 | resources: { 29 | en: { 30 | translation: { 31 | invoiceTitle: 'Invoice', 32 | invoiceNumber: 'Invoice Nº', 33 | invoiceDate: 'Invoice Date', 34 | invoiceDue: 'Due Date', 35 | invoiceBill: 'Bill/Ship To:', 36 | invoiceBillCompanyName: 'Billing company name', 37 | invoiceBillCompanyAddress: 'Billing company address', 38 | invoiceAdditionalInfo: 'Additional info', 39 | invoiceFrom: 'From:', 40 | invoiceFromCompanyName: 'Your company name', 41 | invoiceFromCompanyAddress: 'Your company address', 42 | invoiceDescription: 'Description', 43 | invoiceUnits: 'Units', 44 | invoiceUnitPrice: 'Unit Price', 45 | invoiceAmount: 'Amount', 46 | invoiceAddButton: 'Add Invoice Items', 47 | invoiceAmountDue: 'Amount due', 48 | invoiceModalTitle: 'Fill your services', 49 | invoiceCancelModal: 'Cancel', 50 | invoiceAddItemModal: 'Add Item' 51 | } 52 | }, 53 | es: { 54 | translation: { 55 | invoiceTitle: 'Factura', 56 | invoiceNumber: 'Nº de Factura', 57 | invoiceDate: 'Fecha de Factura', 58 | invoiceDue: 'Fecha de Venc.', 59 | invoiceBill: 'Factura/Envío a:', 60 | invoiceBillCompanyName: 'Nombre de la empresa de facturación', 61 | invoiceBillCompanyAddress: 'Dirección de la empresa de facturación', 62 | invoiceAdditionalInfo: 'Información adicional', 63 | invoiceFrom: 'De:', 64 | invoiceFromCompanyName: 'El nombre de tu compañía', 65 | invoiceFromCompanyAddress: 'Dirección de su empresa', 66 | invoiceDescription: 'Descripción', 67 | invoiceUnits: 'Unidades', 68 | invoiceUnitPrice: 'Precio por Unidad', 69 | invoiceAmount: 'Monto', 70 | invoiceAddButton: 'Añadir elementos de factura', 71 | invoiceAmountDue: 'Monto adeudado', 72 | invoiceModalTitle: 'Llena tus servicios', 73 | invoiceCancelModal: 'Cancelar', 74 | invoiceAddItemModal: 'Añadir Elemento' 75 | } 76 | }, 77 | de: { 78 | translation: { 79 | invoiceTitle: 'Rechnung', 80 | invoiceNumber: 'Rechnungsnummer', 81 | invoiceDate: 'Rechnungsdatum', 82 | invoiceDue: 'Fälligkeitsdatum', 83 | invoiceBill: 'Rechnung/Versand an:', 84 | invoiceBillCompanyName: 'Name der Fakturierungsfirma', 85 | invoiceBillCompanyAddress: 'Adresse der Fakturierungsfirma', 86 | invoiceAdditionalInfo: 'Zusätzliche Informationen', 87 | invoiceFrom: 'Von:', 88 | invoiceFromCompanyName: 'Ihr Firmenname', 89 | invoiceFromCompanyAddress: 'Ihre Firmenadresse', 90 | invoiceDescription: 'Beschreibung', 91 | invoiceUnits: 'Stückzahl', 92 | invoiceUnitPrice: 'Stückpreis', 93 | invoiceAmount: 'Betrag', 94 | invoiceAddButton: 'Rechnungsposten hinzufügen', 95 | invoiceAmountDue: 'Fälliger Betrag', 96 | invoiceModalTitle: 'Ihre Dienstleistungen ausfüllen', 97 | invoiceCancelModal: 'Abbrechen', 98 | invoiceAddItemModal: 'Artikel hinzufügen' 99 | } 100 | } 101 | } 102 | }, 103 | function (err, t) { 104 | // HTML elements 105 | document 106 | .querySelector('[key-lang="invoiceBillCompanyName"]') 107 | .setAttribute('placeholder', t('invoiceBillCompanyName')) 108 | document 109 | .querySelector('[key-lang="invoiceBillCompanyAddress"]') 110 | .setAttribute('placeholder', t('invoiceBillCompanyAddress')) 111 | document 112 | .querySelector('[key-lang="invoiceFromCompanyName"]') 113 | .setAttribute('placeholder', t('invoiceFromCompanyName')) 114 | document 115 | .querySelector('[key-lang="invoiceFromCompanyAddress"]') 116 | .setAttribute('placeholder', t('invoiceFromCompanyAddress')) 117 | document.querySelector('[key-lang="invoiceAddButton"]').innerHTML = t( 118 | 'invoiceAddButton' 119 | ) 120 | document.querySelector('[key-lang="invoiceModalTitle"]').innerHTML = t( 121 | 'invoiceModalTitle' 122 | ) 123 | document.querySelector('[key-lang="invoiceCancelModal"]').innerHTML = t( 124 | 'invoiceCancelModal' 125 | ) 126 | document.querySelector('[key-lang="invoiceAddItemModal"]').innerHTML = t( 127 | 'invoiceAddItemModal' 128 | ) 129 | 130 | // HTML Elements For Loop 131 | document.querySelectorAll('[key-lang="invoiceTitle"]').forEach(item => { 132 | item.innerHTML = t('invoiceTitle') 133 | }) 134 | 135 | document.querySelectorAll('[key-lang="invoiceNumber"]').forEach(item => { 136 | item.innerHTML = t('invoiceNumber') 137 | }) 138 | 139 | document.querySelectorAll('[key-lang="invoiceDate"]').forEach(item => { 140 | item.innerHTML = t('invoiceDate') 141 | }) 142 | 143 | document.querySelectorAll('[key-lang="invoiceDue"]').forEach(item => { 144 | item.innerHTML = t('invoiceDue') 145 | }) 146 | 147 | document.querySelectorAll('[key-lang="invoiceBill"]').forEach(item => { 148 | item.innerHTML = t('invoiceBill') 149 | }) 150 | 151 | document.querySelectorAll('[key-lang="invoiceFrom"]').forEach(item => { 152 | item.innerHTML = t('invoiceFrom') 153 | }) 154 | 155 | document 156 | .querySelectorAll('[key-lang="invoiceDescription"]') 157 | .forEach(item => { 158 | item.innerHTML = t('invoiceDescription') 159 | }) 160 | 161 | document 162 | .querySelectorAll('[key-lang="invoiceAdditionalInfo"]') 163 | .forEach(item => { 164 | item.setAttribute('placeholder', t('invoiceAdditionalInfo')) 165 | }) 166 | 167 | document 168 | .querySelectorAll('[key-lang="invoiceDescription"]') 169 | .forEach(item => { 170 | item.innerHTML = t('invoiceDescription') 171 | }) 172 | 173 | document.querySelectorAll('[key-lang="invoiceUnits"]').forEach(item => { 174 | item.innerHTML = t('invoiceUnits') 175 | }) 176 | 177 | document.querySelectorAll('[key-lang="invoiceUnitPrice"]').forEach(item => { 178 | item.innerHTML = t('invoiceUnitPrice') 179 | }) 180 | 181 | document.querySelectorAll('[key-lang="invoiceAmount"]').forEach(item => { 182 | item.innerHTML = t('invoiceAmount') 183 | }) 184 | 185 | document 186 | .querySelectorAll('[key-lang="invoiceAmountDue"]') 187 | .forEach(item => { 188 | item.innerHTML = t('invoiceAmountDue') 189 | }) 190 | } 191 | ) 192 | 193 | const LANGUAGE_SWITCHER = document.querySelector('#languageSwitcher') 194 | const ENGLISH_BUTTON = document.querySelector('#english') 195 | const SPANISH_BUTTON = document.querySelector('#spanish') 196 | const GERMAN_BUTTON = document.querySelector('#german') 197 | 198 | ENGLISH_BUTTON.addEventListener('click', e => { 199 | e.preventDefault() 200 | 201 | localStorage.setItem('i18nextLng', 'en') 202 | window.location.reload() 203 | }) 204 | 205 | SPANISH_BUTTON.addEventListener('click', e => { 206 | e.preventDefault() 207 | 208 | localStorage.setItem('i18nextLng', 'es') 209 | window.location.reload() 210 | }) 211 | 212 | GERMAN_BUTTON.addEventListener('click', e => { 213 | e.preventDefault() 214 | 215 | localStorage.setItem('i18nextLng', 'de') 216 | window.location.reload() 217 | }) 218 | 219 | if (localStorage.getItem('i18nextLng') === 'en') { 220 | LANGUAGE_SWITCHER.innerHTML = ` English` 221 | } else if ( 222 | localStorage.getItem('i18nextLng') === 'es' || 223 | localStorage.getItem('i18nextLng') === 'es-ES' 224 | ) { 225 | LANGUAGE_SWITCHER.innerHTML = ` Español` 226 | } else if (localStorage.getItem('i18nextLng') === 'de') { 227 | LANGUAGE_SWITCHER.innerHTML = ` German` 228 | } 229 | -------------------------------------------------------------------------------- /src/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/apple-touch-icon.png -------------------------------------------------------------------------------- /src/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/favicon-16x16.png -------------------------------------------------------------------------------- /src/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/favicon-32x32.png -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/mstile-150x150.png -------------------------------------------------------------------------------- /src/static/og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danestves/invoicegenerator/298d987d1f91fb8919fef7af0105223fb1a11108/src/static/og.jpg -------------------------------------------------------------------------------- /src/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.11, written by Peter Selinger 2001-2013 -------------------------------------------------------------------------------- /src/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invoice Generator", 3 | "short_name": "Invoice Generator", 4 | "icons": [ 5 | { 6 | "src": "/static/android-chrome-192x192.png?v=kPvy4M7Kk4", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/static/android-chrome-512x512.png?v=kPvy4M7Kk4", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/style/main.scss: -------------------------------------------------------------------------------- 1 | /* purgecss start ignore */ 2 | @tailwind base; 3 | @tailwind components; 4 | 5 | /* purgecss end ignore */ 6 | 7 | [x-cloak] { 8 | display: none; 9 | } 10 | 11 | @media print { 12 | .no-printme { 13 | display: none; 14 | } 15 | 16 | .printme { 17 | display: block; 18 | } 19 | 20 | body { 21 | line-height: 1.2; 22 | } 23 | } 24 | 25 | @page { 26 | size: a4 portrait; 27 | counter-increment: page; 28 | } 29 | 30 | /* Datepicker */ 31 | .date-input { 32 | background-color: #fff; 33 | border-radius: 10px; 34 | padding: 0.5rem 1rem; 35 | z-index: 2000; 36 | margin: 3px 0 0 0; 37 | border-top: 1px solid #eee; 38 | box-shadow: 39 | 0 10px 15px -3px rgba(0, 0, 0, 0.1), 40 | 0 4px 6px -2px rgba(0, 0, 0, 0.05); 41 | } 42 | 43 | .date-input.is-hidden { 44 | display: none; 45 | } 46 | 47 | .date-input .pika-title { 48 | padding: 0.5rem; 49 | width: 100%; 50 | text-align: center; 51 | } 52 | 53 | .date-input .pika-prev, 54 | .date-input .pika-next { 55 | margin-top: 0; 56 | 57 | /* margin-top: 0.5rem; */ 58 | padding: 0.2rem 0; 59 | cursor: pointer; 60 | color: #4299e1; 61 | text-transform: uppercase; 62 | font-size: 0.85rem; 63 | } 64 | 65 | .date-input .pika-prev { 66 | float: left; 67 | } 68 | 69 | .date-input .pika-next { 70 | float: right; 71 | } 72 | 73 | .date-input .pika-prev:hover, 74 | .date-input .pika-next:hover { 75 | text-decoration: underline; 76 | } 77 | 78 | .date-input .pika-label { 79 | display: inline-block; 80 | font-size: 0; 81 | } 82 | 83 | .date-input .pika-select-month, 84 | .date-input .pika-select-year { 85 | display: inline-block; 86 | border: 1px solid #ddd; 87 | color: #444; 88 | background-color: #fff; 89 | border-radius: 10px; 90 | font-size: 0.9rem; 91 | padding-left: 0.5em; 92 | padding-right: 0.5em; 93 | padding-top: 0.25em; 94 | padding-bottom: 0.25em; 95 | appearance: none; 96 | } 97 | 98 | .date-input .pika-select-month { 99 | margin-right: 0.25em; 100 | } 101 | 102 | .date-input .pika-select-month:focus, 103 | .date-input .pika-select-year:focus { 104 | border-color: #cbd5e0; 105 | outline: none; 106 | } 107 | 108 | .date-input table { 109 | width: 100%; 110 | border-collapse: collapse; 111 | margin-bottom: 0.2rem; 112 | } 113 | 114 | .date-input table th { 115 | width: 2em; 116 | height: 2em; 117 | font-weight: normal; 118 | color: #718096; 119 | text-align: center; 120 | } 121 | 122 | .date-input table th abbr { 123 | text-decoration: none; 124 | } 125 | 126 | .date-input table td { 127 | padding: 2px; 128 | } 129 | 130 | .date-input table td button { 131 | /* border: 1px solid #e2e8f0; */ 132 | width: 1.8em; 133 | height: 1.8em; 134 | text-align: center; 135 | color: #555; 136 | border-radius: 10px; 137 | } 138 | 139 | .date-input table td button:hover { 140 | background-color: #bee3f8; 141 | } 142 | 143 | .date-input table td.is-today button { 144 | background-color: #ebf8ff; 145 | } 146 | 147 | .date-input table td.is-selected button { 148 | background-color: #3182ce; 149 | color: white; 150 | } 151 | 152 | .date-input table td.is-selected button:hover { 153 | color: white; 154 | } 155 | 156 | @tailwind utilities; 157 | 158 | .top-1\/2 { 159 | top: 50%; 160 | } 161 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prefix: '', 3 | important: false, 4 | separator: ':', 5 | theme: { 6 | screens: { 7 | sm: '640px', 8 | md: '768px', 9 | lg: '1024px', 10 | xl: '1280px' 11 | }, 12 | colors: { 13 | transparent: 'transparent', 14 | 15 | black: '#000', 16 | white: '#fff', 17 | 18 | gray: { 19 | 100: '#f7fafc', 20 | 200: '#edf2f7', 21 | 300: '#e2e8f0', 22 | 400: '#cbd5e0', 23 | 500: '#a0aec0', 24 | 600: '#718096', 25 | 700: '#4a5568', 26 | 800: '#2d3748', 27 | 900: '#1a202c' 28 | }, 29 | red: { 30 | 100: '#fff5f5', 31 | 200: '#fed7d7', 32 | 300: '#feb2b2', 33 | 400: '#fc8181', 34 | 500: '#f56565', 35 | 600: '#e53e3e', 36 | 700: '#c53030', 37 | 800: '#9b2c2c', 38 | 900: '#742a2a' 39 | }, 40 | orange: { 41 | 100: '#fffaf0', 42 | 200: '#feebc8', 43 | 300: '#fbd38d', 44 | 400: '#f6ad55', 45 | 500: '#ed8936', 46 | 600: '#dd6b20', 47 | 700: '#c05621', 48 | 800: '#9c4221', 49 | 900: '#7b341e' 50 | }, 51 | yellow: { 52 | 100: '#fffff0', 53 | 200: '#fefcbf', 54 | 300: '#faf089', 55 | 400: '#f6e05e', 56 | 500: '#ecc94b', 57 | 600: '#d69e2e', 58 | 700: '#b7791f', 59 | 800: '#975a16', 60 | 900: '#744210' 61 | }, 62 | green: { 63 | 100: '#f0fff4', 64 | 200: '#c6f6d5', 65 | 300: '#9ae6b4', 66 | 400: '#68d391', 67 | 500: '#48bb78', 68 | 600: '#38a169', 69 | 700: '#2f855a', 70 | 800: '#276749', 71 | 900: '#22543d' 72 | }, 73 | teal: { 74 | 100: '#e6fffa', 75 | 200: '#b2f5ea', 76 | 300: '#81e6d9', 77 | 400: '#4fd1c5', 78 | 500: '#38b2ac', 79 | 600: '#319795', 80 | 700: '#2c7a7b', 81 | 800: '#285e61', 82 | 900: '#234e52' 83 | }, 84 | blue: { 85 | 100: '#ebf8ff', 86 | 200: '#bee3f8', 87 | 300: '#90cdf4', 88 | 400: '#63b3ed', 89 | 500: '#4299e1', 90 | 600: '#3182ce', 91 | 700: '#2b6cb0', 92 | 800: '#2c5282', 93 | 900: '#2a4365' 94 | }, 95 | indigo: { 96 | 100: '#ebf4ff', 97 | 200: '#c3dafe', 98 | 300: '#a3bffa', 99 | 400: '#7f9cf5', 100 | 500: '#667eea', 101 | 600: '#5a67d8', 102 | 700: '#4c51bf', 103 | 800: '#434190', 104 | 900: '#3c366b' 105 | }, 106 | purple: { 107 | 100: '#faf5ff', 108 | 200: '#e9d8fd', 109 | 300: '#d6bcfa', 110 | 400: '#b794f4', 111 | 500: '#9f7aea', 112 | 600: '#805ad5', 113 | 700: '#6b46c1', 114 | 800: '#553c9a', 115 | 900: '#44337a' 116 | }, 117 | pink: { 118 | 100: '#fff5f7', 119 | 200: '#fed7e2', 120 | 300: '#fbb6ce', 121 | 400: '#f687b3', 122 | 500: '#ed64a6', 123 | 600: '#d53f8c', 124 | 700: '#b83280', 125 | 800: '#97266d', 126 | 900: '#702459' 127 | } 128 | }, 129 | spacing: { 130 | px: '1px', 131 | '0': '0', 132 | '1': '0.25rem', 133 | '2': '0.5rem', 134 | '3': '0.75rem', 135 | '4': '1rem', 136 | '5': '1.25rem', 137 | '6': '1.5rem', 138 | '8': '2rem', 139 | '10': '2.5rem', 140 | '12': '3rem', 141 | '16': '4rem', 142 | '20': '5rem', 143 | '24': '6rem', 144 | '32': '8rem', 145 | '40': '10rem', 146 | '48': '12rem', 147 | '56': '14rem', 148 | '64': '16rem' 149 | }, 150 | backgroundColor: theme => theme('colors'), 151 | backgroundPosition: { 152 | bottom: 'bottom', 153 | center: 'center', 154 | left: 'left', 155 | 'left-bottom': 'left bottom', 156 | 'left-top': 'left top', 157 | right: 'right', 158 | 'right-bottom': 'right bottom', 159 | 'right-top': 'right top', 160 | top: 'top' 161 | }, 162 | backgroundSize: { 163 | auto: 'auto', 164 | cover: 'cover', 165 | contain: 'contain' 166 | }, 167 | borderColor: theme => ({ 168 | ...theme('colors'), 169 | default: theme('colors.gray.300', 'currentColor') 170 | }), 171 | borderRadius: { 172 | none: '0', 173 | sm: '0.125rem', 174 | default: '0.25rem', 175 | md: '0.375rem', 176 | lg: '0.5rem', 177 | full: '9999px' 178 | }, 179 | borderWidth: { 180 | default: '1px', 181 | '0': '0', 182 | '2': '2px', 183 | '4': '4px', 184 | '8': '8px' 185 | }, 186 | boxShadow: { 187 | xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', 188 | sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', 189 | default: 190 | '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', 191 | md: 192 | '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', 193 | lg: 194 | '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', 195 | xl: 196 | '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', 197 | '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', 198 | inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', 199 | outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', 200 | none: 'none' 201 | }, 202 | container: {}, 203 | cursor: { 204 | auto: 'auto', 205 | default: 'default', 206 | pointer: 'pointer', 207 | wait: 'wait', 208 | text: 'text', 209 | move: 'move', 210 | 'not-allowed': 'not-allowed' 211 | }, 212 | fill: { 213 | current: 'currentColor' 214 | }, 215 | flex: { 216 | '1': '1 1 0%', 217 | auto: '1 1 auto', 218 | initial: '0 1 auto', 219 | none: 'none' 220 | }, 221 | flexGrow: { 222 | '0': '0', 223 | default: '1' 224 | }, 225 | flexShrink: { 226 | '0': '0', 227 | default: '1' 228 | }, 229 | fontFamily: { 230 | sans: [ 231 | 'system-ui', 232 | '-apple-system', 233 | 'BlinkMacSystemFont', 234 | '"Segoe UI"', 235 | 'Roboto', 236 | '"Helvetica Neue"', 237 | 'Arial', 238 | '"Noto Sans"', 239 | 'sans-serif', 240 | '"Apple Color Emoji"', 241 | '"Segoe UI Emoji"', 242 | '"Segoe UI Symbol"', 243 | '"Noto Color Emoji"' 244 | ], 245 | serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], 246 | mono: [ 247 | 'Menlo', 248 | 'Monaco', 249 | 'Consolas', 250 | '"Liberation Mono"', 251 | '"Courier New"', 252 | 'monospace' 253 | ] 254 | }, 255 | fontSize: { 256 | xs: '0.75rem', 257 | sm: '0.875rem', 258 | base: '1rem', 259 | lg: '1.125rem', 260 | xl: '1.25rem', 261 | '2xl': '1.5rem', 262 | '3xl': '1.875rem', 263 | '4xl': '2.25rem', 264 | '5xl': '3rem', 265 | '6xl': '4rem' 266 | }, 267 | fontWeight: { 268 | hairline: '100', 269 | thin: '200', 270 | light: '300', 271 | normal: '400', 272 | medium: '500', 273 | semibold: '600', 274 | bold: '700', 275 | extrabold: '800', 276 | black: '900' 277 | }, 278 | height: theme => ({ 279 | auto: 'auto', 280 | ...theme('spacing'), 281 | full: '100%', 282 | screen: '100vh' 283 | }), 284 | inset: { 285 | '0': '0', 286 | auto: 'auto' 287 | }, 288 | letterSpacing: { 289 | tighter: '-0.05em', 290 | tight: '-0.025em', 291 | normal: '0', 292 | wide: '0.025em', 293 | wider: '0.05em', 294 | widest: '0.1em' 295 | }, 296 | lineHeight: { 297 | none: '1', 298 | tight: '1.25', 299 | snug: '1.375', 300 | normal: '1.5', 301 | relaxed: '1.625', 302 | loose: '2', 303 | '3': '.75rem', 304 | '4': '1rem', 305 | '5': '1.25rem', 306 | '6': '1.5rem', 307 | '7': '1.75rem', 308 | '8': '2rem', 309 | '9': '2.25rem', 310 | '10': '2.5rem' 311 | }, 312 | listStyleType: { 313 | none: 'none', 314 | disc: 'disc', 315 | decimal: 'decimal' 316 | }, 317 | margin: (theme, { negative }) => ({ 318 | auto: 'auto', 319 | ...theme('spacing'), 320 | ...negative(theme('spacing')) 321 | }), 322 | maxHeight: { 323 | full: '100%', 324 | screen: '100vh' 325 | }, 326 | maxWidth: (theme, { breakpoints }) => ({ 327 | none: 'none', 328 | xs: '20rem', 329 | sm: '24rem', 330 | md: '28rem', 331 | lg: '32rem', 332 | xl: '36rem', 333 | '2xl': '42rem', 334 | '3xl': '48rem', 335 | '4xl': '56rem', 336 | '5xl': '64rem', 337 | '6xl': '72rem', 338 | full: '100%', 339 | ...breakpoints(theme('screens')) 340 | }), 341 | minHeight: { 342 | '0': '0', 343 | full: '100%', 344 | screen: '100vh' 345 | }, 346 | minWidth: { 347 | '0': '0', 348 | full: '100%' 349 | }, 350 | objectPosition: { 351 | bottom: 'bottom', 352 | center: 'center', 353 | left: 'left', 354 | 'left-bottom': 'left bottom', 355 | 'left-top': 'left top', 356 | right: 'right', 357 | 'right-bottom': 'right bottom', 358 | 'right-top': 'right top', 359 | top: 'top' 360 | }, 361 | opacity: { 362 | '0': '0', 363 | '25': '0.25', 364 | '50': '0.5', 365 | '75': '0.75', 366 | '100': '1' 367 | }, 368 | order: { 369 | first: '-9999', 370 | last: '9999', 371 | none: '0', 372 | '1': '1', 373 | '2': '2', 374 | '3': '3', 375 | '4': '4', 376 | '5': '5', 377 | '6': '6', 378 | '7': '7', 379 | '8': '8', 380 | '9': '9', 381 | '10': '10', 382 | '11': '11', 383 | '12': '12' 384 | }, 385 | padding: theme => theme('spacing'), 386 | placeholderColor: theme => theme('colors'), 387 | stroke: { 388 | current: 'currentColor' 389 | }, 390 | strokeWidth: { 391 | '0': '0', 392 | '1': '1', 393 | '2': '2' 394 | }, 395 | textColor: theme => theme('colors'), 396 | width: theme => ({ 397 | auto: 'auto', 398 | ...theme('spacing'), 399 | '1/2': '50%', 400 | '1/3': '33.333333%', 401 | '2/3': '66.666667%', 402 | '1/4': '25%', 403 | '2/4': '50%', 404 | '3/4': '75%', 405 | '1/5': '20%', 406 | '2/5': '40%', 407 | '3/5': '60%', 408 | '4/5': '80%', 409 | '1/6': '16.666667%', 410 | '2/6': '33.333333%', 411 | '3/6': '50%', 412 | '4/6': '66.666667%', 413 | '5/6': '83.333333%', 414 | '1/12': '8.333333%', 415 | '2/12': '16.666667%', 416 | '3/12': '25%', 417 | '4/12': '33.333333%', 418 | '5/12': '41.666667%', 419 | '6/12': '50%', 420 | '7/12': '58.333333%', 421 | '8/12': '66.666667%', 422 | '9/12': '75%', 423 | '10/12': '83.333333%', 424 | '11/12': '91.666667%', 425 | full: '100%', 426 | screen: '100vw' 427 | }), 428 | zIndex: { 429 | auto: 'auto', 430 | '0': '0', 431 | '10': '10', 432 | '20': '20', 433 | '30': '30', 434 | '40': '40', 435 | '50': '50' 436 | }, 437 | gap: theme => theme('spacing'), 438 | gridTemplateColumns: { 439 | none: 'none', 440 | '1': 'repeat(1, minmax(0, 1fr))', 441 | '2': 'repeat(2, minmax(0, 1fr))', 442 | '3': 'repeat(3, minmax(0, 1fr))', 443 | '4': 'repeat(4, minmax(0, 1fr))', 444 | '5': 'repeat(5, minmax(0, 1fr))', 445 | '6': 'repeat(6, minmax(0, 1fr))', 446 | '7': 'repeat(7, minmax(0, 1fr))', 447 | '8': 'repeat(8, minmax(0, 1fr))', 448 | '9': 'repeat(9, minmax(0, 1fr))', 449 | '10': 'repeat(10, minmax(0, 1fr))', 450 | '11': 'repeat(11, minmax(0, 1fr))', 451 | '12': 'repeat(12, minmax(0, 1fr))' 452 | }, 453 | gridColumn: { 454 | auto: 'auto', 455 | 'span-1': 'span 1 / span 1', 456 | 'span-2': 'span 2 / span 2', 457 | 'span-3': 'span 3 / span 3', 458 | 'span-4': 'span 4 / span 4', 459 | 'span-5': 'span 5 / span 5', 460 | 'span-6': 'span 6 / span 6', 461 | 'span-7': 'span 7 / span 7', 462 | 'span-8': 'span 8 / span 8', 463 | 'span-9': 'span 9 / span 9', 464 | 'span-10': 'span 10 / span 10', 465 | 'span-11': 'span 11 / span 11', 466 | 'span-12': 'span 12 / span 12' 467 | }, 468 | gridColumnStart: { 469 | auto: 'auto', 470 | '1': '1', 471 | '2': '2', 472 | '3': '3', 473 | '4': '4', 474 | '5': '5', 475 | '6': '6', 476 | '7': '7', 477 | '8': '8', 478 | '9': '9', 479 | '10': '10', 480 | '11': '11', 481 | '12': '12', 482 | '13': '13' 483 | }, 484 | gridColumnEnd: { 485 | auto: 'auto', 486 | '1': '1', 487 | '2': '2', 488 | '3': '3', 489 | '4': '4', 490 | '5': '5', 491 | '6': '6', 492 | '7': '7', 493 | '8': '8', 494 | '9': '9', 495 | '10': '10', 496 | '11': '11', 497 | '12': '12', 498 | '13': '13' 499 | }, 500 | gridTemplateRows: { 501 | none: 'none', 502 | '1': 'repeat(1, minmax(0, 1fr))', 503 | '2': 'repeat(2, minmax(0, 1fr))', 504 | '3': 'repeat(3, minmax(0, 1fr))', 505 | '4': 'repeat(4, minmax(0, 1fr))', 506 | '5': 'repeat(5, minmax(0, 1fr))', 507 | '6': 'repeat(6, minmax(0, 1fr))' 508 | }, 509 | gridRow: { 510 | auto: 'auto', 511 | 'span-1': 'span 1 / span 1', 512 | 'span-2': 'span 2 / span 2', 513 | 'span-3': 'span 3 / span 3', 514 | 'span-4': 'span 4 / span 4', 515 | 'span-5': 'span 5 / span 5', 516 | 'span-6': 'span 6 / span 6' 517 | }, 518 | gridRowStart: { 519 | auto: 'auto', 520 | '1': '1', 521 | '2': '2', 522 | '3': '3', 523 | '4': '4', 524 | '5': '5', 525 | '6': '6', 526 | '7': '7' 527 | }, 528 | gridRowEnd: { 529 | auto: 'auto', 530 | '1': '1', 531 | '2': '2', 532 | '3': '3', 533 | '4': '4', 534 | '5': '5', 535 | '6': '6', 536 | '7': '7' 537 | }, 538 | transformOrigin: { 539 | center: 'center', 540 | top: 'top', 541 | 'top-right': 'top right', 542 | right: 'right', 543 | 'bottom-right': 'bottom right', 544 | bottom: 'bottom', 545 | 'bottom-left': 'bottom left', 546 | left: 'left', 547 | 'top-left': 'top left' 548 | }, 549 | scale: { 550 | '0': '0', 551 | '50': '.5', 552 | '75': '.75', 553 | '90': '.9', 554 | '95': '.95', 555 | '100': '1', 556 | '105': '1.05', 557 | '110': '1.1', 558 | '125': '1.25', 559 | '150': '1.5' 560 | }, 561 | rotate: { 562 | '-180': '-180deg', 563 | '-90': '-90deg', 564 | '-45': '-45deg', 565 | '0': '0', 566 | '45': '45deg', 567 | '90': '90deg', 568 | '180': '180deg' 569 | }, 570 | translate: (theme, { negative }) => ({ 571 | ...theme('spacing'), 572 | ...negative(theme('spacing')), 573 | '-full': '-100%', 574 | '-1/2': '-50%', 575 | '1/2': '50%', 576 | full: '100%' 577 | }), 578 | skew: { 579 | '-12': '-12deg', 580 | '-6': '-6deg', 581 | '-3': '-3deg', 582 | '0': '0', 583 | '3': '3deg', 584 | '6': '6deg', 585 | '12': '12deg' 586 | }, 587 | transitionProperty: { 588 | none: 'none', 589 | all: 'all', 590 | default: 591 | 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', 592 | colors: 'background-color, border-color, color, fill, stroke', 593 | opacity: 'opacity', 594 | shadow: 'box-shadow', 595 | transform: 'transform' 596 | }, 597 | transitionTimingFunction: { 598 | linear: 'linear', 599 | in: 'cubic-bezier(0.4, 0, 1, 1)', 600 | out: 'cubic-bezier(0, 0, 0.2, 1)', 601 | 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)' 602 | }, 603 | transitionDuration: { 604 | '75': '75ms', 605 | '100': '100ms', 606 | '150': '150ms', 607 | '200': '200ms', 608 | '300': '300ms', 609 | '500': '500ms', 610 | '700': '700ms', 611 | '1000': '1000ms' 612 | } 613 | }, 614 | variants: { 615 | accessibility: ['responsive', 'focus'], 616 | alignContent: ['responsive'], 617 | alignItems: ['responsive'], 618 | alignSelf: ['responsive'], 619 | appearance: ['responsive'], 620 | backgroundAttachment: ['responsive'], 621 | backgroundColor: ['responsive', 'hover', 'dark', 'dark:hover'], 622 | backgroundPosition: ['responsive'], 623 | backgroundRepeat: ['responsive'], 624 | backgroundSize: ['responsive'], 625 | borderCollapse: ['responsive'], 626 | borderColor: ['responsive', 'hover', 'focus', 'dark', 'dark:focus'], 627 | borderRadius: ['responsive'], 628 | borderStyle: ['responsive'], 629 | borderWidth: ['responsive'], 630 | boxShadow: ['responsive', 'hover', 'focus'], 631 | boxSizing: ['responsive'], 632 | cursor: ['responsive'], 633 | display: ['responsive'], 634 | fill: ['responsive'], 635 | flex: ['responsive'], 636 | flexDirection: ['responsive'], 637 | flexGrow: ['responsive'], 638 | flexShrink: ['responsive'], 639 | flexWrap: ['responsive'], 640 | float: ['responsive'], 641 | clear: ['responsive'], 642 | fontFamily: ['responsive'], 643 | fontSize: ['responsive'], 644 | fontSmoothing: ['responsive'], 645 | fontStyle: ['responsive'], 646 | fontWeight: ['responsive', 'hover', 'focus'], 647 | height: ['responsive'], 648 | inset: ['responsive'], 649 | justifyContent: ['responsive'], 650 | letterSpacing: ['responsive'], 651 | lineHeight: ['responsive'], 652 | listStylePosition: ['responsive'], 653 | listStyleType: ['responsive'], 654 | margin: ['responsive'], 655 | maxHeight: ['responsive'], 656 | maxWidth: ['responsive'], 657 | minHeight: ['responsive'], 658 | minWidth: ['responsive'], 659 | objectFit: ['responsive'], 660 | objectPosition: ['responsive'], 661 | opacity: ['responsive', 'hover', 'focus'], 662 | order: ['responsive'], 663 | outline: ['responsive', 'focus'], 664 | overflow: ['responsive'], 665 | padding: ['responsive'], 666 | placeholderColor: ['responsive', 'focus'], 667 | pointerEvents: ['responsive'], 668 | position: ['responsive'], 669 | resize: ['responsive'], 670 | stroke: ['responsive'], 671 | strokeWidth: ['responsive'], 672 | tableLayout: ['responsive'], 673 | textAlign: ['responsive'], 674 | textColor: ['responsive', 'hover', 'focus', 'hover', 'dark', 'dark:hover'], 675 | textDecoration: ['responsive', 'hover', 'focus'], 676 | textTransform: ['responsive'], 677 | userSelect: ['responsive'], 678 | verticalAlign: ['responsive'], 679 | visibility: ['responsive'], 680 | whitespace: ['responsive'], 681 | width: ['responsive'], 682 | wordBreak: ['responsive'], 683 | zIndex: ['responsive'], 684 | gap: ['responsive'], 685 | gridAutoFlow: ['responsive'], 686 | gridTemplateColumns: ['responsive'], 687 | gridColumn: ['responsive'], 688 | gridColumnStart: ['responsive'], 689 | gridColumnEnd: ['responsive'], 690 | gridTemplateRows: ['responsive'], 691 | gridRow: ['responsive'], 692 | gridRowStart: ['responsive'], 693 | gridRowEnd: ['responsive'], 694 | transform: ['responsive'], 695 | transformOrigin: ['responsive'], 696 | scale: ['responsive', 'hover', 'focus'], 697 | rotate: ['responsive', 'hover', 'focus'], 698 | translate: ['responsive', 'hover', 'focus'], 699 | skew: ['responsive', 'hover', 'focus'], 700 | transitionProperty: ['responsive'], 701 | transitionTimingFunction: ['responsive'], 702 | transitionDuration: ['responsive'] 703 | }, 704 | corePlugins: {}, 705 | plugins: [require('@danestves/tailwindcss-darkmode')()] 706 | } 707 | -------------------------------------------------------------------------------- /webpack.config.common.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob') 2 | const path = require('path') 3 | 4 | const CopyWebpackPlugin = require('copy-webpack-plugin') 5 | const HTMLWebpackPlugin = require('html-webpack-plugin') 6 | 7 | const generateHTMLPlugins = () => 8 | glob.sync('./src/**/*.html').map( 9 | dir => 10 | new HTMLWebpackPlugin({ 11 | filename: path.basename(dir), // Output 12 | template: dir, // Input 13 | minify: { 14 | html5: true, 15 | collapseWhitespace: true, 16 | caseSensitive: true, 17 | removeComments: true 18 | } 19 | }) 20 | ) 21 | 22 | module.exports = { 23 | node: { 24 | fs: 'empty' 25 | }, 26 | entry: ['./src/js/app.js', './src/style/main.scss'], 27 | output: { 28 | path: path.resolve(__dirname, 'dist'), 29 | filename: '[name].[hash].js' 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.m?js$/, 35 | exclude: /(node_modules|bower_components)/, 36 | use: { 37 | loader: 'babel-loader', 38 | options: { 39 | presets: ['@babel/preset-env'], 40 | plugins: ['@babel/plugin-transform-runtime'] 41 | } 42 | } 43 | }, 44 | { 45 | test: /\.html$/, 46 | loader: 'raw-loader' 47 | }, 48 | { 49 | test: /\.(pdf|gif|png|jpe?g|svg)$/, 50 | use: [ 51 | { 52 | loader: 'file-loader', 53 | options: { 54 | outputPath: 'static/' 55 | } 56 | } 57 | ] 58 | }, 59 | { 60 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 61 | use: [ 62 | { 63 | loader: 'file-loader', 64 | options: { 65 | name: '[name].[ext]', 66 | outputPath: 'fonts/' 67 | } 68 | } 69 | ] 70 | } 71 | ] 72 | }, 73 | plugins: [ 74 | new CopyWebpackPlugin([ 75 | { 76 | from: './src/static/', 77 | to: './static/' 78 | } 79 | ]), 80 | ...generateHTMLPlugins() 81 | ], 82 | stats: { 83 | colors: true 84 | }, 85 | devtool: 'source-map' 86 | } 87 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | 4 | const common = require('./webpack.config.common.js') 5 | 6 | module.exports = merge(common, { 7 | mode: 'development', 8 | devServer: { 9 | contentBase: 'src', 10 | watchContentBase: true, 11 | hot: true, 12 | port: process.env.PORT || 9000, 13 | host: process.env.HOST || 'localhost' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.(sass|scss)$/, 19 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] 20 | }, 21 | { 22 | test: /\.css$/i, 23 | use: ['style-loader', 'css-loader'] 24 | } 25 | ] 26 | }, 27 | plugins: [new webpack.HotModuleReplacementPlugin()] 28 | }) 29 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const cssnano = require('cssnano') 2 | const merge = require('webpack-merge') 3 | 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 7 | 8 | const common = require('./webpack.config.common.js') 9 | 10 | module.exports = merge(common, { 11 | mode: 'production', 12 | optimization: { 13 | minimize: true 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.(sass|scss)$/, 19 | use: [ 20 | { 21 | loader: MiniCssExtractPlugin.loader 22 | }, 23 | 'css-loader', 24 | 'postcss-loader', 25 | 'sass-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.css$/i, 30 | use: ['style-loader', 'css-loader'] 31 | } 32 | ] 33 | }, 34 | plugins: [ 35 | new CleanWebpackPlugin(), 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].css', 38 | chunkFilename: '[id].css' 39 | }), 40 | new OptimizeCssAssetsPlugin({ 41 | assetNameRegExp: /\.css$/g, 42 | cssProcessor: cssnano, 43 | cssProcessorOptions: { discardComments: { removeAll: true } }, 44 | canPrint: true 45 | }) 46 | ] 47 | }) 48 | --------------------------------------------------------------------------------