├── .husky ├── .gitignore └── pre-commit ├── assets ├── img │ └── .gitkeep ├── readme.md └── styles │ └── app.css ├── tests ├── unit │ ├── fixtures │ │ └── index.ts │ ├── @types │ │ ├── readme.md │ │ ├── shim-vue.d.ts │ │ └── store.d.ts │ ├── mocks │ │ ├── nuxt-color-mode │ │ │ └── index.ts │ │ ├── store │ │ │ ├── sidebar.ts │ │ │ ├── index.ts │ │ │ └── root.ts │ │ ├── nuxt-context │ │ │ └── index.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── nuxt-http │ │ │ └── index.ts │ │ └── index.ts │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── specs │ │ ├── layouts │ │ │ ├── error.spec.ts │ │ │ └── default.spec.ts │ │ ├── components │ │ │ ├── app-links.spec.ts │ │ │ ├── app-header.spec.ts │ │ │ ├── app-switch-theme.spec.ts │ │ │ ├── app-appear-disappear.spec.ts │ │ │ └── app-input.spec.ts │ │ └── pages │ │ │ ├── random-image.spec.ts │ │ │ └── index.spec.ts │ └── setup │ │ └── index.ts ├── readme.md └── e2e │ ├── .eslintrc.js │ ├── fixtures │ └── example.json │ ├── tsconfig.json │ ├── integration │ └── index.spec.ts │ ├── plugins │ └── index.ts │ └── support │ ├── index.ts │ └── commands.ts ├── static ├── _redirects ├── icon.png └── readme.md ├── .env.example ├── @types ├── readme.md ├── shim-vue.d.ts └── store.d.ts ├── prettier.config.js ├── compositions ├── index.ts ├── text.ts ├── mouse-position.ts └── timer.ts ├── babel.config.js ├── components ├── readme.md ├── AppSwitchTheme.vue ├── AppHeader.vue ├── AppLinks.vue ├── AppInput.vue └── AppAppearDisappear.vue ├── .editorconfig ├── layouts ├── readme.md ├── error.vue └── default.vue ├── pages ├── readme.md ├── random-image.vue └── index.vue ├── netlify.toml ├── plugins └── readme.md ├── stylelint.config.js ├── cypress.json ├── middleware └── readme.md ├── store ├── readme.md ├── sidebar.ts └── index.ts ├── .eslintrc.js ├── azure-pipelines.yml ├── tailwind.config.js ├── tsconfig.json ├── .circleci └── config.yml ├── license.md ├── jest.config.js ├── .gitignore ├── package.json ├── readme.md ├── readme-id.md └── nuxt.config.ts /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /assets/img/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GOOGLE_ANALYTICS="" 2 | SENTRY_DSN="" 3 | -------------------------------------------------------------------------------- /@types/readme.md: -------------------------------------------------------------------------------- 1 | # @TYPES 2 | 3 | This directory contains typescript declaration file -------------------------------------------------------------------------------- /tests/unit/@types/readme.md: -------------------------------------------------------------------------------- 1 | # @TYPES 2 | 3 | This directory contains typescript declaration file -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | node_modules/.bin/lint-staged 5 | -------------------------------------------------------------------------------- /@types/shim-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /tests/unit/mocks/nuxt-color-mode/index.ts: -------------------------------------------------------------------------------- 1 | export const mockNuxtColorMode = { 2 | preference: 'dark' 3 | } 4 | -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api/HEAD/static/icon.png -------------------------------------------------------------------------------- /tests/unit/@types/shim-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /tests/unit/mocks/store/sidebar.ts: -------------------------------------------------------------------------------- 1 | export const mockSidebar = { 2 | isDisplay: true, 3 | show: jest.fn(), 4 | hide: jest.fn() 5 | } 6 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'none', 3 | semi: false, 4 | arrowParens: 'always', 5 | singleQuote: true 6 | } 7 | -------------------------------------------------------------------------------- /compositions/index.ts: -------------------------------------------------------------------------------- 1 | export { useTimer } from './timer' 2 | export { useMousePosition } from './mouse-position' 3 | export { useText } from './text' 4 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | 'jest/globals': true 4 | }, 5 | extends: ['plugin:jest/all'], 6 | plugins: ['jest'] 7 | } 8 | -------------------------------------------------------------------------------- /tests/readme.md: -------------------------------------------------------------------------------- 1 | # TEST 2 | 3 | This directory contains your test case. 4 | It consists of unit testing for single file component and e2e testing for whole application. 5 | -------------------------------------------------------------------------------- /tests/unit/mocks/nuxt-context/index.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '@nuxt/types' 2 | 3 | export const mockNuxtContext: Partial = { 4 | redirect: jest.fn() 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/mocks/router/index.ts: -------------------------------------------------------------------------------- 1 | import { Route } from 'vue-router' 2 | 3 | export const mockRoute: Partial = { 4 | query: { 5 | q: 'hello-world' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/unit/mocks/nuxt-http/index.ts: -------------------------------------------------------------------------------- 1 | import { NuxtHTTPInstance } from '@nuxt/http' 2 | 3 | export const mockNuxtHttp: Partial = { 4 | $get: jest.fn() 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/mocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nuxt-color-mode' 2 | export * from './nuxt-context' 3 | export * from './nuxt-http' 4 | export * from './router' 5 | export * from './store' 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | if (api.env('test')) { 3 | return { 4 | presets: [['@babel/env', { targets: { node: 'current' } }]] 5 | } 6 | } 7 | return {} 8 | } 9 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | 'cypress/globals': true 4 | }, 5 | extends: ['../../.eslintrc.js', 'plugin:cypress/recommended'], 6 | plugins: ['cypress'] 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/mocks/store/index.ts: -------------------------------------------------------------------------------- 1 | import { mockRoot } from './root' 2 | import { mockSidebar } from './sidebar' 3 | 4 | export const mockAccessor = { 5 | ...mockRoot, 6 | sidebar: mockSidebar 7 | } 8 | -------------------------------------------------------------------------------- /components/readme.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | The components directory contains your Vue.js Components. 4 | Nuxt.js doesn't supercharge these components. 5 | 6 | **This directory is not required, you can delete it if you don't want to use it.** 7 | -------------------------------------------------------------------------------- /tests/unit/mocks/store/root.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/named 2 | 3 | export const mockRoot = { 4 | user: { 5 | name: 'jefrydco', 6 | age: 22 7 | }, 8 | GET_USER: jest.fn(), 9 | RESET_USER: jest.fn() 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "node", 6 | "jest", 7 | "@nuxt/types", 8 | "@nuxt/typescript-build", 9 | "@nuxt/http" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /layouts/readme.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | This directory contains your Application Layouts. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/views#layouts 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /tests/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "types": [ 6 | "node", 7 | "cypress", 8 | "@nuxt/types", 9 | "@nuxt/typescript-build", 10 | "@nuxt/http" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages/readme.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the .vue files inside this directory and create the router of your application. 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing 8 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Directory (relative to root of your repo) that contains the deploy-ready 3 | # HTML files and assets generated by the build. If a base directory has 4 | # been specified, include it in the publish directory path. 5 | publish = "dist/" 6 | 7 | # Default build command. 8 | command = "yarn generate" -------------------------------------------------------------------------------- /assets/readme.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/assets#webpacked 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /@types/store.d.ts: -------------------------------------------------------------------------------- 1 | import { accessorType } from '~/store' 2 | 3 | export type Accessor = typeof accessorType 4 | 5 | declare module 'vue/types/vue' { 6 | interface Vue { 7 | $accessor: Accessor 8 | } 9 | } 10 | 11 | declare module '@nuxt/types/app' { 12 | interface NuxtAppOptions { 13 | $accessor: Accessor 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/unit/@types/store.d.ts: -------------------------------------------------------------------------------- 1 | import { accessorType } from '~/store' 2 | 3 | export type Accessor = typeof accessorType 4 | 5 | declare module 'vue/types/vue' { 6 | interface Vue { 7 | $accessor: Accessor 8 | } 9 | } 10 | 11 | declare module '@nuxt/types/app' { 12 | interface NuxtAppOptions { 13 | $accessor: Accessor 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /plugins/readme.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | This directory contains your Javascript plugins that you want to run before instantiating the root vue.js application. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/plugins 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /tests/unit/specs/layouts/error.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import ErrorLayout from '~/layouts/error.vue' 3 | 4 | describe('error layout', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(ErrorLayout) 8 | expect(wrapper).toMatchInlineSnapshot(`
`) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /static/readme.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | This directory contains your static files. 4 | Each file inside this directory is mapped to /. 5 | 6 | Example: /static/robots.txt is mapped as /robots.txt. 7 | 8 | More information about the usage of this directory in the documentation: 9 | https://nuxtjs.org/guide/assets#static 10 | 11 | **This directory is not required, you can delete it if you don't want to use it.** 12 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // add your custom config here 3 | // https://stylelint.io/user-guide/configuration 4 | extends: 'stylelint-config-recommended', 5 | rules: { 6 | 'at-rule-no-unknown': [ 7 | true, 8 | { 9 | ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen'] 10 | } 11 | ], 12 | 'block-no-empty': null 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000/", 3 | "fixturesFolder": "tests/e2e/fixtures", 4 | "integrationFolder": "tests/e2e/integration", 5 | "screenshotsFolder": "tests/e2e/screenshots", 6 | "pluginsFile": "tests/e2e/plugins/index.ts", 7 | "videosFolder": "tests/e2e/videos", 8 | "supportFile": "tests/e2e/support/index.ts", 9 | "viewportWidth": 1920, 10 | "viewportHeight": 1080 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/specs/components/app-links.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import AppLinksComponent from '~/components/AppLinks.vue' 3 | 4 | describe('app links component', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(AppLinksComponent) 8 | expect(wrapper).toMatchInlineSnapshot(`
`) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /compositions/text.ts: -------------------------------------------------------------------------------- 1 | import { watch, ref } from '@nuxtjs/composition-api' 2 | import { Accessor } from '~/@types/store' 3 | 4 | export function useText($accessor: Accessor) { 5 | const text = ref('') 6 | 7 | watch( 8 | () => $accessor.user.name, 9 | (userName) => { 10 | text.value = userName 11 | }, 12 | { 13 | immediate: true 14 | } 15 | ) 16 | 17 | return { 18 | text 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /middleware/readme.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | This directory contains your Application Middleware. 4 | The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts). 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing#middleware 8 | 9 | **This directory is not required, you can delete it if you don't want to use it.** 10 | -------------------------------------------------------------------------------- /tests/e2e/integration/index.spec.ts: -------------------------------------------------------------------------------- 1 | describe('Example Test', () => { 2 | it('Visits the app root url', () => { 3 | cy.visit('/') 4 | cy.contains('.btn__set', 'Set User Store') 5 | cy.screenshot() 6 | }) 7 | it('Visits the random image url', () => { 8 | cy.get('a[href="/random-image"]').click() 9 | cy.get('.img__random').should('have.attr', 'alt', 'Random Image') 10 | cy.screenshot() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /store/readme.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /assets/styles/app.css: -------------------------------------------------------------------------------- 1 | /* purgecss start ignore */ 2 | .fade { 3 | &-enter-active, 4 | &-leave-active { 5 | transition: all .3s cubic-bezier(.55, 0, .1, 1); 6 | } 7 | 8 | &-enter, 9 | &-leave-active { 10 | opacity: 0; 11 | } 12 | } 13 | .layout, .page { 14 | &-enter-active, 15 | &-leave-active { 16 | transition: all .3s cubic-bezier(.55, 0, .1, 1); 17 | } 18 | 19 | &-enter, 20 | &-leave-active { 21 | opacity: 0; 22 | } 23 | } 24 | /* purgecss end ignore */ 25 | -------------------------------------------------------------------------------- /tests/unit/specs/components/app-header.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import AppHeaderComponent from '~/components/AppHeader.vue' 3 | 4 | describe('app header component', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(AppHeaderComponent) 8 | expect(wrapper).toMatchInlineSnapshot(` 9 |
10 | This is header that receive props and reversed it: 11 |
12 | `) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /compositions/mouse-position.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api' 2 | 3 | export function useMousePosition() { 4 | const x = ref(0) 5 | const y = ref(0) 6 | 7 | function update(e: MouseEvent) { 8 | x.value = e.pageX 9 | y.value = e.pageY 10 | } 11 | 12 | onMounted(() => { 13 | window.addEventListener('mousemove', update) 14 | }) 15 | 16 | onUnmounted(() => { 17 | window.removeEventListener('mousemove', update) 18 | }) 19 | 20 | return { x, y } 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['vue', 'prettier'], 3 | extends: [ 4 | 'plugin:vue/strongly-recommended', 5 | '@nuxtjs/eslint-config-typescript', 6 | 'plugin:prettier/recommended' 7 | ], 8 | rules: { 9 | 'no-console': 'off', 10 | 'no-param-reassign': 'off', 11 | 'prettier/prettier': 'error', 12 | 'vue/no-v-for-template-key': 'off', 13 | 'vue/no-v-for-template-key-on-child': 'off', 14 | 'vue/no-v-html': 'off' 15 | }, 16 | ignorePatterns: ['buildModules', 'libs'] 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/specs/pages/random-image.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import RandomImagePage from '~/pages/random-image.vue' 3 | 4 | describe('random image page', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(RandomImagePage) 8 | expect(wrapper).toMatchInlineSnapshot( 9 | `
Random Image
` 10 | ) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /components/AppSwitchTheme.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | pool: 7 | vmImage: "vs2017-win2016" 8 | 9 | steps: 10 | - task: NodeTool@0 11 | inputs: 12 | versionSpec: "^10" 13 | displayName: "Install Node.js" 14 | 15 | - script: | 16 | yarn 17 | displayName: "Install Dependencies" 18 | 19 | - script: | 20 | yarn test:unit 21 | displayName: "Run Unit Tests" 22 | 23 | - script: | 24 | yarn coverage 25 | displayName: "Publish Test Result" 26 | -------------------------------------------------------------------------------- /components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /tests/unit/setup/index.ts: -------------------------------------------------------------------------------- 1 | import { config, RouterLinkStub } from '@vue/test-utils' 2 | import { 3 | mockNuxtColorMode, 4 | mockNuxtContext, 5 | mockNuxtHttp, 6 | mockRoute, 7 | mockAccessor 8 | } from '~/tests/unit/mocks' 9 | 10 | config.stubs = { 11 | nuxt: true, 12 | 'no-ssr': true, 13 | 'nuxt-link': RouterLinkStub 14 | } 15 | 16 | config.mocks = { 17 | $accessor: mockAccessor, 18 | $colorMode: mockNuxtColorMode, 19 | $route: mockRoute, 20 | $nuxt: { 21 | context: { 22 | ...mockNuxtContext, 23 | $http: mockNuxtHttp, 24 | route: mockRoute, 25 | app: { 26 | $accessor: mockAccessor 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | // compatible with @nuxtjs/color-mode 4 | darkSelector: '.dark-mode' 5 | }, 6 | variants: { 7 | backgroundColor: [ 8 | 'dark', 9 | 'dark-hover', 10 | 'dark-group-hover', 11 | 'dark-even', 12 | 'dark-odd' 13 | ], 14 | borderColor: ['dark', 'dark-focus', 'dark-focus-within'], 15 | textColor: ['dark', 'dark-hover', 'dark-active'] 16 | }, 17 | plugins: [require('tailwindcss-dark-mode')()], 18 | purge: { 19 | content(contentDefaults) { 20 | return contentDefaults.map((file) => file.replace('.js', '.ts')) 21 | }, 22 | options: { 23 | whitelist: ['dark-mode'] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 15 | const plugins: Cypress.PluginConfig = (on, config) => { 16 | // 17 | } 18 | 19 | module.exports = plugins 20 | -------------------------------------------------------------------------------- /tests/unit/specs/components/app-switch-theme.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import AppSwitchThemeComponent from '~/components/AppSwitchTheme.vue' 3 | 4 | describe('app switch theme component', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(AppSwitchThemeComponent) 8 | expect(wrapper).toMatchInlineSnapshot(` 9 |
14 | `) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/e2e/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "lib": [ 7 | "ESNext", 8 | "ESNext.AsyncIterable", 9 | "DOM" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./*" 23 | ] 24 | }, 25 | "types": [ 26 | "node", 27 | "@nuxt/types", 28 | "@nuxt/typescript-build", 29 | "@nuxt/http", 30 | "@nuxt/content" 31 | ] 32 | }, 33 | "exclude": [ 34 | "tests", 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /tests/unit/specs/components/app-appear-disappear.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import AppAppearDisappearComponent from '~/components/AppAppearDisappear.vue' 3 | 4 | describe('app appear disappear component', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(AppAppearDisappearComponent) 8 | expect(wrapper).toMatchInlineSnapshot(` 9 |
10 |

11 | Disappear in 12 | 10 13 |

14 |
"" -
17 |
18 | `) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/unit/specs/layouts/default.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import DefaultLayout from '~/layouts/default.vue' 3 | 4 | describe('default layout', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(DefaultLayout) 8 | expect(wrapper).toMatchInlineSnapshot(` 9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | `) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /store/sidebar.ts: -------------------------------------------------------------------------------- 1 | import { getterTree, mutationTree, actionTree } from 'nuxt-typed-vuex' 2 | 3 | export type isDisplayPayload = { 4 | isDisplay: boolean 5 | } 6 | 7 | export const state = () => ({ 8 | isDisplay: false 9 | }) 10 | 11 | export const getters = getterTree(state, { 12 | getDisplay(state) { 13 | return state.isDisplay 14 | } 15 | }) 16 | 17 | export const mutations = mutationTree(state, { 18 | SHOW(state) { 19 | state.isDisplay = true 20 | }, 21 | HIDE(state) { 22 | state.isDisplay = false 23 | } 24 | }) 25 | 26 | export const actions = actionTree( 27 | { state, getters, mutations }, 28 | { 29 | async show() { 30 | await this.app.$accessor.sidebar.SHOW() 31 | }, 32 | async hide() { 33 | await this.app.$accessor.sidebar.HIDE() 34 | } 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /tests/unit/specs/components/app-input.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import AppInputComponent from '~/components/AppInput.vue' 3 | 4 | describe('app input component', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(AppInputComponent) 8 | expect(wrapper).toMatchInlineSnapshot( 9 | `
` 10 | ) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: cypress/base:12 6 | environment: 7 | ## this enables colors in the output 8 | TERM: xterm 9 | working_directory: ~/app 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | keys: 14 | - v1-deps-{{ .Branch }}-{{ checksum "yarn.lock" }} 15 | - v1-deps-{{ .Branch }} 16 | - v1-deps 17 | - run: 18 | name: Install Dependencies 19 | command: yarn --ignore-engines 20 | - save_cache: 21 | key: v1-deps-{{ .Branch }}-{{ checksum "yarn.lock" }} 22 | paths: 23 | - ~/.cache ## cache both npm and Cypress! 24 | - run: 25 | name: Run Unit Test 26 | command: yarn test:unit 27 | - run: 28 | name: Publish Test Result 29 | command: yarn coverage 30 | -------------------------------------------------------------------------------- /components/AppLinks.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jefry Dewangga 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | verbose: true, 4 | setupFilesAfterEnv: ['/tests/unit/setup/index.ts'], 5 | moduleNameMapper: { 6 | '^@/(.*)$': '/$1', 7 | '^~/(.*)$': '/$1', 8 | '^vue$': 'vue/dist/vue.common.js' 9 | }, 10 | transform: { 11 | // process `*.vue` files with `vue-jest` 12 | '.*\\.(vue)$': 'vue-jest', 13 | // process js with `ts-jest` 14 | '^.+\\.js$': 'ts-jest', 15 | // Stub non js file 16 | '.+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$': 17 | 'jest-transform-stub' 18 | }, 19 | collectCoverage: true, 20 | collectCoverageFrom: [ 21 | '**/components/**/*.{ts,vue}', 22 | '**/layouts/**/*.{ts,vue}', 23 | '**/pages/**/*.{ts,vue}', 24 | '!**/node_modules/**', 25 | '!**/coverage/**' 26 | ], 27 | coverageDirectory: '/tests/unit/coverage', 28 | snapshotSerializers: ['jest-serializer-vue'], 29 | transformIgnorePatterns: [ 30 | 'node_modules/(?!vee-validate/dist/rules|@nuxtjs/composition-api)' 31 | ], 32 | globals: { 33 | 'ts-jest': { 34 | tsconfig: '/tests/unit/tsconfig.json' 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | -------------------------------------------------------------------------------- /pages/random-image.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 59 | -------------------------------------------------------------------------------- /components/AppInput.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 51 | -------------------------------------------------------------------------------- /tests/unit/specs/pages/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import IndexPage from '~/pages/index.vue' 3 | 4 | describe('index page', () => { 5 | it('renders to snapshot', () => { 6 | expect.assertions(1) 7 | const wrapper = shallowMount(IndexPage) 8 | expect(wrapper).toMatchInlineSnapshot(` 9 |
10 | 11 | 12 | 13 | 14 |
    15 |
  1. User: jefrydco
  2. 16 |
  3. Age: 22
  4. 17 |
18 |
23 |
    24 |
  • X: 0
  • 25 |
  • Y: 0
  • 26 |
27 |
28 | `) 29 | }) 30 | it('has a query params', () => { 31 | expect.assertions(1) 32 | const wrapper = shallowMount(IndexPage) 33 | expect(wrapper.vm.$route.query).toStrictEqual({ q: 'hello-world' }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getAccessorType, 3 | getterTree, 4 | mutationTree, 5 | actionTree 6 | } from 'nuxt-typed-vuex' 7 | import * as sidebar from './sidebar' 8 | 9 | export type UserPayload = { 10 | user: { 11 | name: string 12 | age: number 13 | } 14 | } 15 | 16 | export type TokenPayload = { 17 | token: string 18 | } 19 | 20 | export const state = () => ({ 21 | user: { 22 | name: '', 23 | age: 0 24 | }, 25 | token: '' 26 | }) 27 | 28 | export const getters = getterTree(state, { 29 | getUserName(state) { 30 | return state.user.name 31 | }, 32 | getUserAge(state) { 33 | return state.user.age 34 | } 35 | }) 36 | 37 | export const mutations = mutationTree(state, { 38 | SET_USER(state, { user }: UserPayload) { 39 | state.user = user 40 | }, 41 | RESET_USER(state) { 42 | state.user = { 43 | name: '', 44 | age: 0 45 | } 46 | }, 47 | SET_TOKEN(state, { token }: TokenPayload) { 48 | state.token = token 49 | } 50 | }) 51 | 52 | export const actions = actionTree( 53 | { state, getters, mutations }, 54 | { 55 | async GET_USER() { 56 | await this.app.$accessor.SET_USER({ 57 | user: { 58 | name: 'jefrydco', 59 | age: 22 60 | } 61 | }) 62 | }, 63 | async GET_TOKEN() { 64 | await this.app.$accessor.SET_TOKEN({ 65 | token: 'lorem-ipsum' 66 | }) 67 | } 68 | } 69 | ) 70 | 71 | export const accessorType = getAccessorType({ 72 | state, 73 | getters, 74 | mutations, 75 | actions, 76 | modules: { 77 | sidebar 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /compositions/timer.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref, watch } from '@nuxtjs/composition-api' 2 | import { Accessor } from '~/@types/store' 3 | 4 | export function useTimer($accessor: Accessor, callback: Function) { 5 | const timer = ref(10000) 6 | const millisecond = 1000 7 | let intervalDisappearId: unknown 8 | let intervalAppearId: unknown 9 | 10 | function initAppear() { 11 | timer.value = 10000 12 | intervalAppearId = setInterval(() => { 13 | timer.value -= millisecond 14 | if (timer.value === 0) { 15 | $accessor.sidebar.show() 16 | clearInterval(intervalAppearId as number) 17 | initDisappear() 18 | } 19 | }, millisecond) 20 | } 21 | 22 | function initDisappear() { 23 | timer.value = 10000 24 | $accessor.sidebar.show() 25 | intervalDisappearId = setInterval(() => { 26 | timer.value -= millisecond 27 | if (timer.value === 0) { 28 | $accessor.sidebar.hide() 29 | clearInterval(intervalDisappearId as number) 30 | callback() 31 | initAppear() 32 | } 33 | }, millisecond) 34 | } 35 | 36 | onMounted(() => { 37 | initDisappear() 38 | }) 39 | 40 | watch(timer, (newTimer, oldTimer) => { 41 | console.log({ newTimer, oldTimer }) 42 | }) 43 | 44 | function reset() { 45 | timer.value = 10000 46 | if (intervalAppearId) { 47 | clearInterval(intervalAppearId as number) 48 | } 49 | if (intervalDisappearId) { 50 | clearInterval(intervalDisappearId as number) 51 | } 52 | initDisappear() 53 | } 54 | 55 | return { 56 | timer, 57 | reset 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /components/AppAppearDisappear.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 73 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # VSCode History Plugin 119 | .history 120 | 121 | # Cypress screenshots and videos 122 | tests/e2e/screenshots 123 | tests/e2e/videos 124 | 125 | # Nuxt Plugin 126 | static/sw.js 127 | static/browserconfig.xml 128 | static/OneSignalSDKUpdaterWorker.js 129 | static/OneSignalSDKWorker.js 130 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-typescript-pwa-tailwindcss-composition-api", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "Nuxt Typescript PWA TailwindCSS Composition API Starter", 6 | "homepage": "https://github.com/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api.git" 10 | }, 11 | "license": "MIT", 12 | "author": { 13 | "name": "Jefry Dewangga", 14 | "url": "https://jefrydco.id" 15 | }, 16 | "scripts": { 17 | "build": "nuxt build --modern", 18 | "dev": "nuxt", 19 | "generate": "nuxt generate --modern", 20 | "lint": "eslint --ext .ts,.vue .", 21 | "start": "nuxt start", 22 | "test": "npm run test:unit", 23 | "test:e2e": "cypress run", 24 | "test:e2e:open": "cypress open", 25 | "test:unit": "jest ./tests/unit", 26 | "coverage": "codecov" 27 | }, 28 | "dependencies": { 29 | "@nuxt/http": "^0.6.4", 30 | "@nuxt/typescript-runtime": "^2.1.0", 31 | "@nuxtjs/browserconfig": "^0.0.13", 32 | "@nuxtjs/composition-api": "^0.22.0", 33 | "@nuxtjs/google-analytics": "^2.4.0", 34 | "@nuxtjs/pwa": "^3.3.5", 35 | "@nuxtjs/sentry": "^5.0.2", 36 | "dotenv": "^8.2.0", 37 | "nuxt": "^2.15.3", 38 | "nuxt-typed-vuex": "^0.1.22", 39 | "vee-validate": "^3.4.5" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.13.10", 43 | "@babel/preset-env": "^7.13.10", 44 | "@nuxt/content": "^1.14.0", 45 | "@nuxt/types": "^2.15.3", 46 | "@nuxt/typescript-build": "^2.1.0", 47 | "@nuxtjs/color-mode": "^2.0.5", 48 | "@nuxtjs/eslint-config-typescript": "^6.0.0", 49 | "@nuxtjs/eslint-module": "^3.0.2", 50 | "@nuxtjs/stylelint-module": "^4.0.0", 51 | "@nuxtjs/tailwindcss": "^3.4.3", 52 | "@types/jest": "^26.0.20", 53 | "@types/node": "^14.14.33", 54 | "@vue/test-utils": "^1.1.3", 55 | "babel-core": "^7.0.0-bridge.0", 56 | "babel-jest": "^26.6.3", 57 | "codecov": "^3.8.1", 58 | "cypress": "^6.6.0", 59 | "eslint": "^7.21.0", 60 | "eslint-config-prettier": "^8.1.0", 61 | "eslint-loader": "^4.0.2", 62 | "eslint-plugin-cypress": "^2.11.2", 63 | "eslint-plugin-jest": "^24.2.1", 64 | "eslint-plugin-prettier": "^3.3.1", 65 | "eslint-plugin-vue": "7.7.0", 66 | "husky": "^5.1.3", 67 | "jest": "^26.6.3", 68 | "jest-serializer-vue": "^2.0.2", 69 | "jest-transform-stub": "^2.0.0", 70 | "lint-staged": "^10.5.4", 71 | "prettier": "^2.2.1", 72 | "stylelint": "^13.12.0", 73 | "stylelint-config-recommended": "^4.0.0", 74 | "tailwindcss-dark-mode": "^1.1.7", 75 | "ts-jest": "^26.5.3", 76 | "vue-jest": "^3.0.7" 77 | }, 78 | "husky": { 79 | "hooks": { 80 | "pre-commit": "lint-staged" 81 | } 82 | }, 83 | "lint-staged": { 84 | "*.{ts,vue}": [ 85 | "eslint --ext .ts,.vue --fix" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Nuxt Typescript PWA Tailwind CSS Composition API Starter 2 | 3 | [![Netlify Status](https://api.netlify.com/api/v1/badges/b739d022-860a-4c43-a681-1bde9600ec51/deploy-status)](https://app.netlify.com/sites/nuxt-typescript-pwa-tailwindcss-composition-api/deploys) 4 | [![CircleCI Build Status](https://badgen.net/circleci/github/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api/master)](https://circleci.com/gh/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api) 5 | [![Azure Build Status](https://dev.azure.com/jefrydco/jefrydco/_apis/build/status/jefrydco.nuxt-typescript-pwa-tailwindcss-composition-api?branchName=master)](https://dev.azure.com/jefrydco/jefrydco/_build/latest?definitionId=5&branchName=master) 6 | [![Coverage Status](https://badgen.net/codecov/c/github/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api/master)](https://codecov.io/gh/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api) 7 | 8 | > Nuxt.js + Typescript + PWA + Tailwind CSS + Composition API starter project 9 | 10 | **⚠️ Please be aware that this starter project contains experimental composition API features that may subject unstable or changed in the future** 11 | 12 | ## Languages 13 | 14 | Read this description in another languages: 15 | 16 | - [Indonesian](./readme-id.md) 17 | 18 | ## Features 19 | 20 | This starter project includes official Nuxt.js modules: 21 | 22 | - [Http](https://github.com/nuxt/http) 23 | - [Google Analytics](https://github.com/nuxt-community/analytics-module) 24 | - [PWA](https://github.com/nuxt-community/pwa-module) 25 | - [Sentry](https://github.com/nuxt-community/sentry-module) 26 | - [Nuxt TailwindCSS](https://tailwindcss.nuxtjs.org/) 27 | - [Color Mode](https://github.com/nuxt-community/color-mode-module) 28 | - [Nuxt Composition API](https://composition-api.nuxtjs.org/) 29 | 30 | It is also enriched with the best Vue's UI framework: 31 | 32 | - [Tailwind CSS](https://tailwindcss.com/) 33 | - [VeeValidate](https://logaretm.github.io/vee-validate) 34 | 35 | and for the best typed experience, it also includes: 36 | 37 | - [Nuxt Typed Vuex](https://nuxt-typed-vuex.roe.dev/) 38 | 39 | ## Setup 40 | 41 | ```bash 42 | # install dependencies 43 | $ yarn # Or npm install 44 | 45 | # serve with hot reload at localhost:3000 46 | $ yarn dev 47 | 48 | # build for production and launch server 49 | $ yarn build 50 | $ yarn start 51 | 52 | # generate static project 53 | $ yarn generate 54 | 55 | # Run unit test 56 | $ yarn test 57 | 58 | # Run E2E test 59 | $ yarn dev 60 | $ yarn test:e2e 61 | ``` 62 | 63 | ## Sentry and Google Analytics 64 | 65 | To activate Sentry and Google Analytics: 66 | 67 | 1. Rename `.env.example` to `.env` 68 | 2. Change your Sentry DSN and Google Analytics tracking identity config in `.env` file 69 | 3. Uncomment the modules configuration code in `nuxt.config.ts` 70 | 71 | ## More Information 72 | 73 | For detailed explanation on how things work, check out the [Nuxt.js](https://nuxtjs.org/) and [Tailwind CSS](https://tailwindcss.com/) documentation. 74 | 75 | ## License 76 | 77 | [MIT License](./license.md) 78 | 79 | Copyright (c) Jefry Dewangga ([@jefrydco](https://jefrydco.id)) 80 | -------------------------------------------------------------------------------- /readme-id.md: -------------------------------------------------------------------------------- 1 | # Nuxt Typescript PWA TailwindCSS Composition API 2 | 3 | [![Netlify Status](https://api.netlify.com/api/v1/badges/b739d022-860a-4c43-a681-1bde9600ec51/deploy-status)](https://app.netlify.com/sites/nuxt-typescript-pwa-tailwindcss-composition-api/deploys) 4 | [![CircleCI Build Status](https://badgen.net/circleci/github/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api/master)](https://circleci.com/gh/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api) 5 | [![Azure Build Status](https://dev.azure.com/jefrydco/jefrydco/_apis/build/status/jefrydco.nuxt-typescript-pwa-tailwindcss-composition-api?branchName=master)](https://dev.azure.com/jefrydco/jefrydco/_build/latest?definitionId=5&branchName=master) 6 | [![Coverage Status](https://badgen.net/codecov/c/github/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api/master)](https://codecov.io/gh/jefrydco/nuxt-typescript-pwa-tailwindcss-composition-api) 7 | 8 | > Proyek awalan untuk Nuxt.js + Typescript + PWA + TailwindCSS + Composition API 9 | 10 | **⚠️ Perlu diketahui bahwa proyek awalan ini berisi fitur eksperimental _Composition API_ yang mungkin tidak stabil atau berubah di kemudian hari** 11 | 12 | ## Bahasa 13 | 14 | Baca deskripsi ini di bahasa lain: 15 | 16 | - [English](./readme.md) 17 | 18 | ## Fitur 19 | 20 | Proyek awalan ini menyertakan beberapa modul resmi Nuxt.js berikut: 21 | 22 | - [Http](https://github.com/nuxt/http) 23 | - [Google Analytics](https://github.com/nuxt-community/analytics-module) 24 | - [PWA](https://github.com/nuxt-community/pwa-module) 25 | - [Sentry](https://github.com/nuxt-community/sentry-module) 26 | - [Nuxt TailwindCSS](https://tailwindcss.nuxtjs.org/) 27 | - [Color Mode](https://github.com/nuxt-community/color-mode-module) 28 | - [Nuxt Composition API](https://composition-api.nuxtjs.org/) 29 | 30 | juga diperkaya dengan kerangka kerja antarmuka Vue terbaik berikut: 31 | 32 | - [Tailwind CSS](https://tailwindcss.com/) 33 | - [VeeValidate](https://logaretm.github.io/vee-validate) 34 | 35 | dan untuk pengalaman pengembang terbaik, juga menyertakan: 36 | 37 | - [Nuxt Typed Vuex](https://nuxt-typed-vuex.roe.dev/) 38 | 39 | ## Persiapan 40 | 41 | ```bash 42 | # memasang dependensi 43 | $ yarn # Atau npm install 44 | 45 | # menjalankan dengan _hot reload_ di localhost:3000 46 | $ yarn dev 47 | 48 | # _build_ untuk produksi dan meluncurkan peladen 49 | $ yarn build 50 | $ yarn start 51 | 52 | # _generate_ untuk proyek statis 53 | $ yarn generate 54 | 55 | # Menjalankan tes unit 56 | $ yarn test 57 | 58 | # Menjalankan tes E2E 59 | $ yarn dev 60 | $ yarn test:e2e 61 | ``` 62 | 63 | ## Sentry dan _Google Analytics_ 64 | 65 | Untuk mengaktifkan Sentry dan _Google Analytics_: 66 | 67 | 1. Ganti nama `.env.example` menjadi `.env` 68 | 2. Ubah konfigurasi DSN Sentry dan identitas pelacak _Google Analytics_ pada berkas `.env` 69 | 3. Hapus komentar pada kode konfigurasi pada berkas `nuxt.config.ts` 70 | 71 | ## Informasi Lebih Lanjut 72 | 73 | Untuk informasi lebih lanjut, silahkan cek dokumentasi resmi [Nuxt.js](https://id.nuxtjs.org/) dan [Tailwind CSS](https://tailwindcss.com/). 74 | 75 | ## Lisensi 76 | 77 | [Lisensi MIT](./license.md) 78 | 79 | Hak Cipta (c) Jefry Dewangga ([@jefrydco](https://jefrydco.id)) -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { NuxtOptions } from '@nuxt/types' 2 | import DotEnv from 'dotenv' 3 | 4 | DotEnv.config() 5 | 6 | export default { 7 | // https://nuxtjs.org/guides/configuration-glossary/configuration-target 8 | target: 'static', 9 | 10 | // https://nuxtjs.org/guides/configuration-glossary/configuration-head 11 | head: { 12 | titleTemplate(pageTitle) { 13 | if (pageTitle) { 14 | return `${pageTitle} - Nuxt Typescript PWA TailwindCSS Composition API` 15 | } 16 | return 'Nuxt Typescript PWA TailwindCSS Composition API' 17 | } 18 | }, 19 | 20 | // https://nuxtjs.org/guides/configuration-glossary/configuration-plugins 21 | plugins: [], 22 | 23 | // https://nuxtjs.org/guides/configuration-glossary/configuration-css 24 | css: ['~/assets/styles/app'], 25 | 26 | // https://nuxtjs.org/guides/configuration-glossary/configuration-modules 27 | modules: [ 28 | // https://http.nuxtjs.org/ 29 | '@nuxt/http', 30 | 31 | // https://pwa.nuxtjs.org/ 32 | '@nuxtjs/pwa', 33 | 34 | // https://github.com/nuxt-community/modules/tree/master/packages/browserconfig 35 | '@nuxtjs/browserconfig' 36 | 37 | // TODO: Uncomment this line to use sentry and google analytics 38 | // // https://github.com/nuxt-community/sentry-module 39 | // '@nuxtjs/sentry', 40 | 41 | // // https://github.com/nuxt-community/analytics-module 42 | // [ 43 | // '@nuxtjs/google-analytics', 44 | // { 45 | // // TODO: Change this id to your Google Analytics ID 46 | // id: process.env.GOOGLE_ANALYTICS 47 | // } 48 | // ] 49 | ], 50 | 51 | // https://nuxtjs.org/guides/configuration-glossary/configuration-modules#buildmodules 52 | buildModules: [ 53 | // https://typescript.nuxtjs.org/guide/setup.html#installation 54 | '@nuxt/typescript-build', 55 | 56 | // https://composition-api.nuxtjs.org/ 57 | '@nuxtjs/composition-api', 58 | 59 | // https://nuxt-typed-vuex.roe.dev/ 60 | 'nuxt-typed-vuex', 61 | 62 | // https://tailwindcss.nuxtjs.org/ 63 | '@nuxtjs/tailwindcss', 64 | 65 | // https://github.com/nuxt-community/stylelint-module 66 | '@nuxtjs/stylelint-module', 67 | 68 | // https://github.com/nuxt-community/eslint-module 69 | '@nuxtjs/eslint-module', 70 | 71 | // https://github.com/nuxt-community/color-mode-module 72 | '@nuxtjs/color-mode' 73 | ], 74 | 75 | // https://typescript.nuxtjs.org/guide/lint.html 76 | typescript: { 77 | typeCheck: { 78 | eslint: { 79 | files: './**/*.{ts,vue}' 80 | } 81 | } 82 | }, 83 | 84 | // https://github.com/nuxt-community/eslint-module 85 | eslint: { 86 | fix: true 87 | }, 88 | 89 | // https://github.com/nuxt-community/stylelint-module 90 | stylelint: { 91 | fix: true 92 | }, 93 | 94 | // https://tailwindcss.nuxtjs.org/ 95 | tailwindcss: { 96 | viewer: false 97 | }, 98 | 99 | // https://pwa.nuxtjs.org 100 | pwa: { 101 | // https://pwa.nuxtjs.org/modules/meta.html 102 | meta: { 103 | name: 'Nuxt Typescript PWA TailwindCSS Composition API', 104 | description: 'Nuxt Typescript PWA TailwindCSS Composition API Starter', 105 | lang: 'id', 106 | ogHost: 107 | 'https://nuxt-typescript-pwa-tailwindcss-composition-api-starter.netlify.app', 108 | twitterCard: 'summary_large_image', 109 | twitterSite: '@jefrydco', 110 | twitterCreator: '@jefrydco' 111 | }, 112 | 113 | // https://pwa.nuxtjs.org/modules/manifest.html 114 | manifest: { 115 | name: 'Nuxt Typescript PWA TailwindCSS Composition API', 116 | short_name: 'Nuxt Typescript PWA TailwindCSS Composition API', 117 | description: 'Nuxt Typescript PWA TailwindCSS Composition API Starter', 118 | lang: 'id', 119 | theme_color: '#4299E1' 120 | }, 121 | 122 | // https://pwa.nuxtjs.org/modules/workbox.html 123 | workbox: { 124 | offlineAnalytics: true, 125 | runtimeCaching: [ 126 | { 127 | urlPattern: 'https://ajax.cloudflare.com/.*', 128 | handler: 'staleWhileRevalidate' 129 | }, 130 | { 131 | urlPattern: 'https://fonts.gstatic.com/.*', 132 | handler: 'staleWhileRevalidate' 133 | }, 134 | { 135 | urlPattern: 'https://www.google.com/.*', 136 | handler: 'staleWhileRevalidate' 137 | }, 138 | { 139 | urlPattern: 'https://www.gstatic.com/.*', 140 | handler: 'staleWhileRevalidate' 141 | } 142 | ] 143 | } 144 | }, 145 | 146 | browserconfig: { 147 | TileColor: '#4299E1' 148 | }, 149 | 150 | // https://nuxtjs.org/guides/configuration-glossary/configuration-generate 151 | generate: { 152 | // choose to suit your project 153 | interval: 3000 154 | }, 155 | 156 | // https://nuxtjs.org/api/configuration-build 157 | build: { 158 | postcss: { 159 | plugins: { 160 | 'postcss-nested': {} 161 | } 162 | }, 163 | transpile: [/typed-vuex/, 'vee-validate/dist/rules'] 164 | } 165 | } as Partial 166 | --------------------------------------------------------------------------------