Skip to content

Commit

Permalink
feat: use a --bare flag to generate a template without too much boi…
Browse files Browse the repository at this point in the history
…lerplate (#636)

Closes #112
Closes #186
Closes #300
Closes #637
  • Loading branch information
haoqunjiang authored Dec 23, 2024
1 parent d4999b7 commit b3d661e
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 21 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ jobs:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
verification-script:
- pnpm --filter "\!*typescript*" build
- pnpm --filter "*typescript*" build
- pnpm --filter "*vitest*" test:unit
- pnpm --filter "*eslint*" lint --no-fix --max-warnings=0
- pnpm --filter "*prettier*" format --write --check
- pnpm --filter '!*typescript*' build
- pnpm --filter '*typescript*' build
- pnpm --filter '*vitest*' test:unit
- pnpm --filter '*eslint*' lint --no-fix --max-warnings=0
- pnpm --filter '*prettier*' format --write --check
# FIXME: it's failing now
# - pnpm --filter "*with-tests*" test:unit
# - pnpm --filter '*with-tests*' test:unit
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.os == 'windows-latest' }}
env:
Expand Down Expand Up @@ -163,11 +163,12 @@ jobs:

- name: Run build script
working-directory: ./playground
run: pnpm --filter "*${{ matrix.e2e-framework }}*" build
run: pnpm --filter '*${{ matrix.e2e-framework }}*' build

- name: Run e2e test script
working-directory: ./playground
run: pnpm --filter "*${{ matrix.e2e-framework }}*" --workspace-concurrency 1 test:e2e
# bare templates can't pass e2e tests because their page structures don't match the example tests
run: pnpm --filter '*${{ matrix.e2e-framework }}*' --filter '!*bare*' --workspace-concurrency 1 test:e2e

- name: Cypress component testing for projects without Vitest
if: ${{ contains(matrix.e2e-framework, 'cypress') }}
Expand Down
28 changes: 25 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import generateReadme from './utils/generateReadme'
import getCommand from './utils/getCommand'
import getLanguage from './utils/getLanguage'
import renderEslint from './utils/renderEslint'
import trimBoilerplate from './utils/trimBoilerplate'

function isValidPackageName(projectName) {
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName)
Expand Down Expand Up @@ -83,7 +84,9 @@ async function init() {
// --playwright
// --eslint
// --eslint-with-prettier (only support prettier through eslint for simplicity)
// --force (for force overwriting)
// in addition to the feature flags, you can also pass the following options:
// --bare (for a barebone template without example code)
// --force (for force overwriting without confirming)

const args = process.argv.slice(2)

Expand Down Expand Up @@ -319,8 +322,8 @@ async function init() {
packageName = projectName ?? defaultProjectName,
shouldOverwrite = argv.force,
needsJsx = argv.jsx,
needsTypeScript = argv.ts || argv.typescript,
needsRouter = argv.router || argv['vue-router'],
needsTypeScript = (argv.ts || argv.typescript) as boolean,
needsRouter = (argv.router || argv['vue-router']) as boolean,
needsPinia = argv.pinia,
needsVitest = argv.vitest || argv.tests,
needsPrettier = argv['eslint-with-prettier'],
Expand Down Expand Up @@ -563,6 +566,25 @@ async function init() {
)
}

if (argv.bare) {
trimBoilerplate(root, { needsTypeScript, needsRouter })
render('bare/base')

// TODO: refactor the `render` utility to avoid this kind of manual mapping?
if (needsTypeScript) {
render('bare/typescript')
}
if (needsVitest) {
render('bare/vitest')
}
if (needsCypressCT) {
render('bare/cypress-ct')
}
if (needsNightwatchCT) {
render('bare/nightwatch-ct')
}
}

// Instructions:
// Supported package managers: pnpm > yarn > bun > npm
const userAgent = process.env.npm_config_user_agent ?? ''
Expand Down
21 changes: 11 additions & 10 deletions scripts/snapshot.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ if (!/pnpm/.test(process.env.npm_config_user_agent ?? ''))
throw new Error("Please use pnpm ('pnpm run snapshot') to generate snapshots!")

const featureFlags = [
'bare',
'typescript',
'jsx',
'router',
Expand Down Expand Up @@ -54,12 +55,7 @@ function fullCombination(arr) {
}

let flagCombinations = fullCombination(featureFlags)
flagCombinations.push(
['default'],
['router', 'pinia'],
['eslint'],
['eslint-with-prettier'],
)
flagCombinations.push(['default'], ['bare', 'default'], ['eslint'], ['eslint-with-prettier'])

// `--with-tests` are equivalent of `--vitest --cypress`
// Previously it means `--cypress` without `--vitest`.
Expand All @@ -85,10 +81,15 @@ for (const flags of flagCombinations) {
}

// Filter out combinations that are not allowed
flagCombinations = flagCombinations.filter(
(combination) =>
!featureFlagsDenylist.some((denylist) => denylist.every((flag) => combination.includes(flag))),
)
flagCombinations = flagCombinations
.filter(
(combination) =>
!featureFlagsDenylist.some((denylist) =>
denylist.every((flag) => combination.includes(flag)),
),
)
// `--bare` is a supplementary flag and should not be used alone
.filter((combination) => !(combination.length === 1 && combination[0] === 'bare'))

const bin = path.posix.relative('../playground/', '../outfile.cjs')

Expand Down
7 changes: 7 additions & 0 deletions template/bare/base/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup></script>

<template>
<h1>Hello World</h1>
</template>

<style scoped></style>
8 changes: 8 additions & 0 deletions template/bare/cypress-ct/src/__tests__/App.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import App from '../App.vue'

describe('App', () => {
it('mounts and renders properly', () => {
cy.mount(App)
cy.get('h1').should('contain', 'Hello World')
})
})
14 changes: 14 additions & 0 deletions template/bare/nightwatch-ct/src/__tests__/App.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
describe('App', function () {
before((browser) => {
browser.init()
})

it('mounts and renders properly', async function () {
const appComponent = await browser.mountComponent('/src/App.vue');

browser.expect.element(appComponent).to.be.present;
browser.expect.element('h1').text.to.contain('Hello World');
})

after((browser) => browser.end())
})
7 changes: 7 additions & 0 deletions template/bare/typescript/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<h1>Hello World</h1>
</template>

<style scoped></style>
11 changes: 11 additions & 0 deletions template/bare/vitest/src/__tests__/App.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'

import { mount } from '@vue/test-utils'
import App from '../App.vue'

describe('App', () => {
it('mounts renders properly', () => {
const wrapper = mount(App)
expect(wrapper.text()).toContain('Hello World')
})
})
36 changes: 36 additions & 0 deletions utils/trimBoilerplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as fs from 'node:fs'
import * as path from 'path'

function replaceContent(filepath: string, replacer: (content: string) => string) {
const content = fs.readFileSync(filepath, 'utf8')
fs.writeFileSync(filepath, replacer(content))
}

export default function trimBoilerplate(rootDir: string, features: Record<string, boolean>) {
const isTs = features.needsTypeScript
const srcDir = path.resolve(rootDir, 'src')

for (const filename of fs.readdirSync(srcDir)) {
// Keep `main.js/ts`, `router`, and `stores` directories
// `App.vue` would be re-rendered in the next step
if (['main.js', 'main.ts', 'router', 'stores'].includes(filename)) {
continue
}
const fullpath = path.resolve(srcDir, filename)
fs.rmSync(fullpath, { recursive: true })
}

// Remove CSS import in the entry file
const entryPath = path.resolve(rootDir, isTs ? 'src/main.ts' : 'src/main.js')
replaceContent(entryPath, (content) => content.replace("import './assets/main.css'\n\n", ''))

// If `router` feature is selected, use an empty router configuration
if (features.needsRouter) {
const routerEntry = path.resolve(srcDir, isTs ? 'router/index.ts' : 'router/index.js')
replaceContent(routerEntry, (content) =>
content
.replace(`import HomeView from '../views/HomeView.vue'\n`, '')
.replace(/routes:\s*\[[\s\S]*?\],/, 'routes: [],'),
)
}
}

0 comments on commit b3d661e

Please sign in to comment.