Skip to content

Commit

Permalink
feat: add nuxt template (#10)
Browse files Browse the repository at this point in the history
* feat: add nuxt template

* chore: update nuxt template task status

* chore: rename nuxt template folder

* chore: use `import.meta.dev` instead ìmport.meta.env.DEV`

* chore: fix nuxt config generation

* chore: don't use coalescing operator on description

* chore: include dependencies and instructions

* chore: use `nypm` to detect package manager

* chore: use offline instead prompt for vif logic

* chore: update index.vue page

* chore: include dependencies and missing workbox/injectManifest options

* chore: add some comments
  • Loading branch information
userquin authored May 10, 2024
1 parent 86793d2 commit 64a1268
Show file tree
Hide file tree
Showing 21 changed files with 538 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Our plan is to release the first version when all Vite templates are ready:
- [x] `SolidJS` and `SolidJS + TypeScript` templates

Later we will add support for meta-frameworks:
- [ ] `Nuxt 3` template
- [x] `Nuxt 3` template
- [ ] `SvelteKit` template
- [ ] `Astro` template
- [ ] `Remix` template
Expand Down
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- [x] `react` and `react-ts` templates
- [x] `preact` and `preact-ts` templates
- [x] `solid` and `solid-ts` templates
- [ ] `nuxt` template
- [x] `nuxt` template
- [ ] `sveltekit` template
- [ ] `astro` template
- [ ] `remix` template
Expand All @@ -28,7 +28,7 @@
- [x] `react` and `react-ts` templates
- [x] `preact` and `preact-ts` templates
- [x] `solid` and `solid-ts` templates
- [ ] `nuxt` template
- [x] `nuxt` template
- [ ] `sveltekit` template
- [ ] `astro` template
- [ ] `remix` template
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"kolorist": "^1.8.0",
"magicast": "^0.3.4",
"minimist": "^1.2.8",
"nypm": "^0.3.8",
"prompts": "^2.4.2",
"tsx": "^4.9.1",
"typescript": "^5.4.5",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

306 changes: 304 additions & 2 deletions src/customize/nuxt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,307 @@
import type { PromptsData } from '../types'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { addNuxtModule } from 'magicast/helpers'
import { generateCode, parseModule } from 'magicast'
import { detectPackageManager } from 'nypm'
import type { PackageJsonEntry, PromptsData } from '../types'
import { preparePWAOptions } from '../pwa'
import { MagicastViteOptions } from '../vite'
import { addPackageObject } from '../utils'
import { includeDependencies } from '../dependencies'

export function customize(_prompts: PromptsData) {
export async function customize(prompts: PromptsData) {
const {
cdProjectName,
templateDir,
rootPath,
customServiceWorker,
prompt,
pwaAssets,
} = prompts
// cleanup target folder
fs.rmSync(path.join(rootPath, 'app.vue'), { recursive: true })
fs.rmSync(path.join(rootPath, 'nuxt.config.ts'), { recursive: true })
fs.rmSync(path.join(rootPath, 'public'), { recursive: true })
// extract template-nuxt folder
fs.copyFileSync(path.join(templateDir, 'app.vue'), path.join(rootPath, 'app.vue'))
fs.mkdirSync(path.join(rootPath, 'layouts'))
fs.writeFileSync(
path.join(rootPath, 'layouts', 'default.vue'),
createLayout(prompts),
'utf-8',
)
fs.mkdirSync(path.join(rootPath, 'pages'))
fs.copyFileSync(
path.join(templateDir, 'pages', 'index.vue'),
path.join(rootPath, 'pages', 'index.vue'),
)
fs.mkdirSync(path.join(rootPath, 'public'))
fs.copyFileSync(path.join(templateDir, 'public', 'favicon.svg'), path.join(rootPath, 'public', 'favicon.svg'))
fs.copyFileSync(path.join(templateDir, 'pwa-assets.config.ts'), path.join(rootPath, 'pwa-assets.config.ts'))
if (customServiceWorker) {
fs.mkdirSync(path.join(rootPath, 'service-worker'))
fs.copyFileSync(
path.join(templateDir, 'service-worker', `${prompt ? 'prompt' : 'claims'}-sw.ts`),
path.join(rootPath, 'service-worker', 'sw.ts'),
)
fs.copyFileSync(
path.join(templateDir, 'service-worker', 'tsconfig.json'),
path.join(rootPath, 'service-worker', 'tsconfig.json'),
)
}

// create nuxt.config.ts
createNuxtConf(prompts)

// prepare package.json
const pkg = JSON.parse(fs.readFileSync(path.join(rootPath, 'package.json'), 'utf-8'))

const pkgManager = await detectPackageManager(rootPath).then(res => res?.name || 'npm')

// dependencies
pkg.dependencies ??= {}
const dependencies: PackageJsonEntry[] = [
['@vite-pwa/assets-generator', '^0.2.4'],
['@vite-pwa/nuxt', '^0.7.0'],
['vite-plugin-pwa', '^0.20.0'],
['workbox-build', '^7.1.0'],
['workbox-window', '^7.1.0'],
]
if (customServiceWorker) {
dependencies.push(
['workbox-core', '^7.1.0'],
['workbox-precaching', '^7.1.0'],
['workbox-routing', '^7.1.0'],
['workbox-strategies', '^7.1.0'],
)
}
addPackageObject('dependencies', dependencies, pkg, true)
// devDependencies
pkg.devDependencies ??= {}
addPackageObject('devDependencies', [
['typescript', '^5.4.5'],
['vue-tsc', '^2.0.16'],
], pkg, true)
// script + resolutions: ignoring dev dependencies
includeDependencies(prompts, pkgManager === 'npm', pkg, true)

// save package.json
fs.writeFileSync(path.join(rootPath, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8')

console.log('\n\nPWA configuration done. Now run:\n')
if (rootPath !== process.cwd()) {
console.log(
` cd ${
cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
}`,
)
}
switch (pkgManager) {
case 'yarn':
console.log(' yarn')
if (!pwaAssets)
console.log(' yarn generate-pwa-icons')
console.log(' yarn dev')
break
default:
console.log(` ${pkgManager} install`)
if (!pwaAssets)
console.log(` ${pkgManager} run generate-pwa-icons`)
console.log(` ${pkgManager} run dev`)
break
}
console.log()
}

function createNuxtConf(prompts: PromptsData) {
const {
rootPath,
customServiceWorker,
reloadSW,
installPWA,
} = prompts
// add nuxt config file
const pwaOptions = preparePWAOptions(true, prompts, 'service-worker', {
workbox: {
globPatterns: ['**/*.{js,css,html,svg,png,ico}'],
cleanupOutdatedCaches: true,
clientsClaim: true,
},
injectManifest: {
globPatterns: ['**/*.{js,css,html,svg,png,ico}'],
},
devOptions: {
enabled: false,
suppressWarnings: true,
navigateFallback: '/',
navigateFallbackAllowlist: [/^\/$/],
type: 'module',
},
})
pwaOptions.registerWebManifestInRouteRules = true
if (reloadSW || installPWA) {
pwaOptions.client = {}
if (reloadSW)
pwaOptions.client.periodicSyncForUpdates = 3600

if (installPWA)
pwaOptions.client.installPrompt = true
}

const nuxtConf = customServiceWorker
? parseModule(`// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
typescript: {
tsConfig: {
exclude: ['../service-worker'],
},
},
devtools: { enabled: true },
nitro: {
prerender: {
routes: ['/'],
},
},
})
`)
: parseModule(`// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
nitro: {
prerender: {
routes: ['/'],
},
},
})
`)
addNuxtModule(nuxtConf, '@vite-pwa/nuxt', 'pwa', pwaOptions)
fs.writeFileSync(
path.join(rootPath, 'nuxt.config.ts'),
generateCode(nuxtConf, MagicastViteOptions).code,
'utf-8',
)
}

function createLayout(prompts: PromptsData) {
const {
offline,
installPWA,
} = prompts
let content = `<script setup lang="ts">
// you can extract the <ClientOnly> component to a separate file
</script>
<template>
<main>
<slot />
<ClientOnly>`
if (offline) {
content += `
<div
v-if="$pwa?.offlineReady || $pwa?.needRefresh"
class="pwa-toast"
role="alert"
aria-labelledby="toast-message"
>
<div class="message">
<span id="toast-message">
{{ $pwa.offlineReady ? 'App ready to work offline' : 'New content available, click on reload button to update' }}
</span>
</div>
<div class="buttons">
<button
v-if="$pwa.needRefresh"
@click="$pwa.updateServiceWorker()"
>
Reload
</button>
<button @click="$pwa.cancelPrompt()">
Close
</button>
</div>
</div>`
}
else {
content += `
<div
v-if="$pwa?.needRefresh"
class="pwa-toast"
role="alert"
aria-labelledby="toast-message"
>
<div class="message">
<span id="toast-message">
New content available, click on reload button to update
</span>
</div>
<div class="buttons">
<button @click="$pwa.updateServiceWorker()">
Reload
</button>
<button @click="$pwa.cancelPrompt()">
Close
</button>
</div>
</div>`
}

if (installPWA) {
const vif = offline
? '$pwa?.showInstallPrompt && !$pwa?.offlineReady && !$pwa?.needRefresh'
: '$pwa?.showInstallPrompt && !$pwa?.needRefresh'
content += `
<div
v-if="${vif}"
class="pwa-toast"
role="alert"
aria-labelledby="install-pwa"
>
<div class="message">
<span id="install-pwa">
Install PWA
</span>
</div>
<button @click="$pwa.install()">
Install
</button>
<button @click="$pwa.cancelInstall()">
Cancel
</button>
</div>`
}

content += `
</ClientOnly>
</main>
</template>
<style>
.pwa-toast {
position: fixed;
right: 0;
bottom: 0;
margin: 16px;
padding: 12px;
border: 1px solid #8885;
border-radius: 4px;
z-index: 1;
text-align: left;
box-shadow: 3px 4px 5px 0 #8885;
background-color: white;
}
.pwa-toast .message {
margin-bottom: 8px;
}
.pwa-toast button {
border: 1px solid #8885;
outline: none;
margin-right: 5px;
border-radius: 2px;
padding: 3px 10px;
}
</style>
`

return content
}
4 changes: 2 additions & 2 deletions src/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { addPackageObject } from './utils'
import type { PromptsData } from './types'

export function includeDependencies(prompts: PromptsData, npmPM: boolean, pkg: any) {
export function includeDependencies(prompts: PromptsData, npmPM: boolean, pkg: any, ignoreDevDependencies = false) {
const { customServiceWorker, pwaAssets } = prompts
if (!pwaAssets) {
addPackageObject(
Expand All @@ -12,7 +12,7 @@ export function includeDependencies(prompts: PromptsData, npmPM: boolean, pkg: a
)
}

if (customServiceWorker) {
if (customServiceWorker && !ignoreDevDependencies) {
addPackageObject(
'devDependencies',
[
Expand Down
Loading

0 comments on commit 64a1268

Please sign in to comment.