foobara-empty-typescript-react-project-generator 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/src/empty_typescript_react_project_config.rb +1 -0
- data/src/empty_typescript_react_project_generator.rb +8 -2
- data/src/generate_empty_typescript_react_project.rb +0 -3
- data/src/generators/robots_txt_generator.rb +13 -0
- data/src/generators/routes_ts_generator.rb +13 -0
- data/src/write_empty_typescript_react_project_to_disk.rb +49 -4
- data/templates/.oxlintrc.json +17 -0
- data/templates/public/_redirects +1 -0
- data/templates/public/robots.txt.erb +5 -0
- data/templates/scripts/generate-sitemap.ts +28 -0
- data/templates/scripts/prerender.ts +53 -0
- data/templates/scripts/routes.ts.erb +25 -0
- data/templates/src/App.tsx +24 -0
- data/templates/src/main.tsx +28 -0
- data/templates/src/pages/About.tsx +12 -0
- data/templates/src/pages/Home.tsx +12 -0
- data/templates/src/pages/NotFound.tsx +11 -0
- metadata +18 -3
- data/templates/eslint.config.js +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f7b4ac21e56483eddd6d15620cf1edaaccd9448e227d17dd2fc73f33c1d38d6
|
|
4
|
+
data.tar.gz: 7b6a39dbbf4e8e24e739af7cc782677f5dc10139d4971d40b473935c5763a030
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 324c237a91f5351a2b885d541b364d6593c5341960cdcf021851926f841ffdb21815bee05dc1b46926808eed81c400a21d5e397e8ae919462e8af9d889306151
|
|
7
|
+
data.tar.gz: cae37cc52abad3774ec99bfbc02c2719cee244d900f6a2efe9384177011302837cf27a6a6d5bcae07964d3ff7145b73c2cd0d008745f31bb8ab262d0e4488821
|
data/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,10 @@ module Foobara
|
|
|
7
7
|
def manifest_to_generator_classes(manifest)
|
|
8
8
|
case manifest
|
|
9
9
|
when EmptyTypescriptReactProjectConfig
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
[
|
|
11
|
+
Generators::RobotsTxtGenerator,
|
|
12
|
+
Generators::RoutesTsGenerator
|
|
13
|
+
]
|
|
12
14
|
else
|
|
13
15
|
# :nocov:
|
|
14
16
|
raise "Not sure how build a generator for a #{manifest}"
|
|
@@ -16,6 +18,10 @@ module Foobara
|
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
21
|
+
|
|
22
|
+
alias empty_typescript_react_project_config relevant_manifest
|
|
23
|
+
|
|
24
|
+
def templates_dir = "#{__dir__}/../templates"
|
|
19
25
|
end
|
|
20
26
|
end
|
|
21
27
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative "../empty_typescript_react_project_generator"
|
|
2
|
+
|
|
3
|
+
module Foobara
|
|
4
|
+
module Generators
|
|
5
|
+
module EmptyTypescriptReactProjectGenerator
|
|
6
|
+
module Generators
|
|
7
|
+
class RobotsTxtGenerator < EmptyTypescriptReactProjectGenerator
|
|
8
|
+
def template_path = ["public", "robots.txt.erb"]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative "../empty_typescript_react_project_generator"
|
|
2
|
+
|
|
3
|
+
module Foobara
|
|
4
|
+
module Generators
|
|
5
|
+
module EmptyTypescriptReactProjectGenerator
|
|
6
|
+
module Generators
|
|
7
|
+
class RoutesTsGenerator < EmptyTypescriptReactProjectGenerator
|
|
8
|
+
def template_path = ["scripts", "routes.ts.erb"]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -56,7 +56,6 @@ module Foobara
|
|
|
56
56
|
def run_pre_generation_tasks
|
|
57
57
|
run_npm_create_vite
|
|
58
58
|
npm_install
|
|
59
|
-
eslint_fix
|
|
60
59
|
git_init
|
|
61
60
|
git_add_all
|
|
62
61
|
git_commit_initial_setup
|
|
@@ -85,7 +84,11 @@ module Foobara
|
|
|
85
84
|
end
|
|
86
85
|
|
|
87
86
|
def run_post_generation_tasks
|
|
88
|
-
|
|
87
|
+
npm_install_react_router
|
|
88
|
+
npm_install_sitemap_and_prerender_dependencies
|
|
89
|
+
add_build_scripts_to_package_json
|
|
90
|
+
remove_static_title_from_index_html
|
|
91
|
+
oxlint_fix
|
|
89
92
|
git_add_all
|
|
90
93
|
git_commit_generated_files
|
|
91
94
|
git_branch_main
|
|
@@ -97,10 +100,52 @@ module Foobara
|
|
|
97
100
|
end
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
def
|
|
103
|
+
def npm_install_react_router
|
|
104
|
+
puts "installing react-router..."
|
|
105
|
+
|
|
106
|
+
cmd = "npm install react-router-dom"
|
|
107
|
+
|
|
108
|
+
Dir.chdir project_directory do
|
|
109
|
+
run_cmd_and_write_output(cmd)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def npm_install_sitemap_and_prerender_dependencies
|
|
114
|
+
puts "installing sitemap/pre-render dependencies..."
|
|
115
|
+
|
|
116
|
+
cmd = "npm install --save-dev puppeteer tsx @types/node"
|
|
117
|
+
|
|
118
|
+
Dir.chdir project_directory do
|
|
119
|
+
run_cmd_and_write_output(cmd)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def add_build_scripts_to_package_json
|
|
124
|
+
puts "adding prebuild/postbuild scripts..."
|
|
125
|
+
|
|
126
|
+
Dir.chdir project_directory do
|
|
127
|
+
run_cmd_and_write_output(
|
|
128
|
+
'npm pkg set scripts.prebuild="npx puppeteer browsers install chrome && ' \
|
|
129
|
+
'tsx scripts/generate-sitemap.ts"'
|
|
130
|
+
)
|
|
131
|
+
run_cmd_and_write_output(
|
|
132
|
+
'npm pkg set scripts.postbuild="tsx scripts/prerender.ts"'
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Pages now set their own <title> in React 19
|
|
138
|
+
def remove_static_title_from_index_html
|
|
139
|
+
index_html_path = File.join(project_directory, "index.html")
|
|
140
|
+
contents = File.read(index_html_path)
|
|
141
|
+
|
|
142
|
+
File.write(index_html_path, contents.sub(%r{\s*<title>.*</title>}, ""))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def oxlint_fix
|
|
101
146
|
puts "linting..."
|
|
102
147
|
|
|
103
|
-
cmd = "npx
|
|
148
|
+
cmd = "npx oxlint --fix"
|
|
104
149
|
Dir.chdir project_directory do
|
|
105
150
|
run_cmd_and_write_output(cmd)
|
|
106
151
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["react", "typescript", "oxc"],
|
|
4
|
+
"rules": {
|
|
5
|
+
"react/rules-of-hooks": "error",
|
|
6
|
+
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
|
7
|
+
},
|
|
8
|
+
"overrides": [
|
|
9
|
+
{
|
|
10
|
+
"files": ["src/domains/**/*.{ts,tsx}"],
|
|
11
|
+
"rules": {
|
|
12
|
+
"typescript/no-explicit-any": "off",
|
|
13
|
+
"typescript/prefer-as-const": "off"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* /index.html 200
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs'
|
|
2
|
+
import { resolve, dirname } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { SITE_URL, getSitemapPages } from './routes.ts'
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = dirname(__filename)
|
|
8
|
+
|
|
9
|
+
const allPages = getSitemapPages()
|
|
10
|
+
|
|
11
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
12
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
13
|
+
${allPages
|
|
14
|
+
.map(
|
|
15
|
+
(page) => ` <url>
|
|
16
|
+
<loc>${SITE_URL}${page.loc}</loc>
|
|
17
|
+
<changefreq>${page.changefreq}</changefreq>
|
|
18
|
+
<priority>${page.priority}</priority>${page.lastmod ? `\n <lastmod>${page.lastmod}</lastmod>` : ''}
|
|
19
|
+
</url>`,
|
|
20
|
+
)
|
|
21
|
+
.join('\n')}
|
|
22
|
+
</urlset>
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
const sitemapPath = resolve(__dirname, '../public/sitemap.xml')
|
|
26
|
+
writeFileSync(sitemapPath, sitemap)
|
|
27
|
+
|
|
28
|
+
console.log(`Generated sitemap with ${allPages.length} URLs`)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from 'fs'
|
|
2
|
+
import { resolve, dirname } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { preview } from 'vite'
|
|
5
|
+
import puppeteer from 'puppeteer'
|
|
6
|
+
import { getRoutePaths } from './routes.ts'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = dirname(__filename)
|
|
10
|
+
const projectRoot = resolve(__dirname, '..')
|
|
11
|
+
const distDir = resolve(projectRoot, 'dist')
|
|
12
|
+
|
|
13
|
+
const routes = getRoutePaths()
|
|
14
|
+
|
|
15
|
+
const server = await preview({ root: projectRoot })
|
|
16
|
+
const serverUrl = server.resolvedUrls!.local[0]
|
|
17
|
+
|
|
18
|
+
const browser = await puppeteer.launch({
|
|
19
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Render everything before writing anything to deal with / prerendering
|
|
23
|
+
const rendered: { route: string; outputPath: string; html: string }[] = []
|
|
24
|
+
|
|
25
|
+
for (const route of routes) {
|
|
26
|
+
const page = await browser.newPage()
|
|
27
|
+
|
|
28
|
+
await page.evaluateOnNewDocument(() => {
|
|
29
|
+
document.addEventListener('prerender-ready', () => {
|
|
30
|
+
;(window as Window & { __prerenderReady?: boolean }).__prerenderReady = true
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
await page.goto(new URL(route, serverUrl).href, { waitUntil: 'networkidle0' })
|
|
35
|
+
await page.waitForFunction('window.__prerenderReady === true')
|
|
36
|
+
|
|
37
|
+
const outputPath = route === '/' ? 'index.html' : `${route.replace(/^\//, '')}.html`
|
|
38
|
+
rendered.push({ route, outputPath, html: await page.content() })
|
|
39
|
+
|
|
40
|
+
await page.close()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const { route, outputPath, html } of rendered) {
|
|
44
|
+
const outputFile = resolve(distDir, outputPath)
|
|
45
|
+
|
|
46
|
+
mkdirSync(dirname(outputFile), { recursive: true })
|
|
47
|
+
writeFileSync(outputFile, html)
|
|
48
|
+
console.log(`Pre-rendered ${route} -> dist/${outputPath}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await browser.close()
|
|
52
|
+
server.httpServer.close()
|
|
53
|
+
process.exit(0)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const SITE_URL = '<%= site_url %>'
|
|
2
|
+
|
|
3
|
+
export interface SitemapPage {
|
|
4
|
+
loc: string
|
|
5
|
+
changefreq: string
|
|
6
|
+
priority: string
|
|
7
|
+
lastmod?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const STATIC_PAGES: SitemapPage[] = [
|
|
11
|
+
{ loc: '/', changefreq: 'monthly', priority: '1.0' },
|
|
12
|
+
{ loc: '/about', changefreq: 'monthly', priority: '0.8' },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
export function getDynamicPages(): SitemapPage[] {
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getSitemapPages(): SitemapPage[] {
|
|
20
|
+
return [...STATIC_PAGES, ...getDynamicPages()]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getRoutePaths(): string[] {
|
|
24
|
+
return getSitemapPages().map((page) => page.loc)
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NavLink, Route, Routes } from 'react-router-dom'
|
|
2
|
+
import './App.css'
|
|
3
|
+
import Home from './pages/Home'
|
|
4
|
+
import About from './pages/About'
|
|
5
|
+
import NotFound from './pages/NotFound'
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="App">
|
|
10
|
+
<nav>
|
|
11
|
+
<NavLink to="/">Home</NavLink> | <NavLink to="/about">About</NavLink>
|
|
12
|
+
</nav>
|
|
13
|
+
<main>
|
|
14
|
+
<Routes>
|
|
15
|
+
<Route path="/" element={<Home />} />
|
|
16
|
+
<Route path="/about" element={<About />} />
|
|
17
|
+
<Route path="*" element={<NotFound />} />
|
|
18
|
+
</Routes>
|
|
19
|
+
</main>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default App
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot, hydrateRoot } from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
4
|
+
import './index.css'
|
|
5
|
+
import App from './App.tsx'
|
|
6
|
+
|
|
7
|
+
const rootElement = document.getElementById('root')!
|
|
8
|
+
|
|
9
|
+
const app = (
|
|
10
|
+
<StrictMode>
|
|
11
|
+
<BrowserRouter>
|
|
12
|
+
<App />
|
|
13
|
+
</BrowserRouter>
|
|
14
|
+
</StrictMode>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if (rootElement.hasChildNodes()) {
|
|
18
|
+
hydrateRoot(rootElement, app)
|
|
19
|
+
} else {
|
|
20
|
+
createRoot(rootElement).render(app)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Not sure if this is necessary but gives some extra time for things to settle before pre-rendering
|
|
24
|
+
requestAnimationFrame(() => {
|
|
25
|
+
requestAnimationFrame(() => {
|
|
26
|
+
document.dispatchEvent(new Event('prerender-ready'))
|
|
27
|
+
})
|
|
28
|
+
})
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foobara-empty-typescript-react-project-generator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miles Georgi
|
|
@@ -58,10 +58,22 @@ files:
|
|
|
58
58
|
- src/empty_typescript_react_project_config.rb
|
|
59
59
|
- src/empty_typescript_react_project_generator.rb
|
|
60
60
|
- src/generate_empty_typescript_react_project.rb
|
|
61
|
+
- src/generators/robots_txt_generator.rb
|
|
62
|
+
- src/generators/routes_ts_generator.rb
|
|
61
63
|
- src/write_empty_typescript_react_project_to_disk.rb
|
|
62
64
|
- templates/.env
|
|
63
65
|
- templates/.github/workflows/tests.yml
|
|
64
|
-
- templates
|
|
66
|
+
- templates/.oxlintrc.json
|
|
67
|
+
- templates/public/_redirects
|
|
68
|
+
- templates/public/robots.txt.erb
|
|
69
|
+
- templates/scripts/generate-sitemap.ts
|
|
70
|
+
- templates/scripts/prerender.ts
|
|
71
|
+
- templates/scripts/routes.ts.erb
|
|
72
|
+
- templates/src/App.tsx
|
|
73
|
+
- templates/src/main.tsx
|
|
74
|
+
- templates/src/pages/About.tsx
|
|
75
|
+
- templates/src/pages/Home.tsx
|
|
76
|
+
- templates/src/pages/NotFound.tsx
|
|
65
77
|
homepage: https://github.com/foobara/generators-empty-typescript-react-project-generator
|
|
66
78
|
licenses:
|
|
67
79
|
- Apache-2.0
|
|
@@ -79,13 +91,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
91
|
- - ">="
|
|
80
92
|
- !ruby/object:Gem::Version
|
|
81
93
|
version: 3.4.0
|
|
94
|
+
- - "<"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 4.1.0
|
|
82
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
98
|
requirements:
|
|
84
99
|
- - ">="
|
|
85
100
|
- !ruby/object:Gem::Version
|
|
86
101
|
version: '0'
|
|
87
102
|
requirements: []
|
|
88
|
-
rubygems_version:
|
|
103
|
+
rubygems_version: 4.0.10
|
|
89
104
|
specification_version: 4
|
|
90
105
|
summary: Generates empty typescript react projects
|
|
91
106
|
test_files: []
|
data/templates/eslint.config.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import globals from 'globals'
|
|
3
|
-
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
-
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
-
import tseslint from 'typescript-eslint'
|
|
6
|
-
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
-
|
|
8
|
-
export default defineConfig([
|
|
9
|
-
globalIgnores(['dist']),
|
|
10
|
-
{
|
|
11
|
-
files: ['**/*.{ts,tsx}'],
|
|
12
|
-
extends: [
|
|
13
|
-
js.configs.recommended,
|
|
14
|
-
tseslint.configs.recommended,
|
|
15
|
-
reactHooks.configs.flat.recommended,
|
|
16
|
-
reactRefresh.configs.vite,
|
|
17
|
-
],
|
|
18
|
-
languageOptions: {
|
|
19
|
-
ecmaVersion: 2020,
|
|
20
|
-
globals: globals.browser,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
files: ['src/domains/**/*.{ts,tsx}'],
|
|
25
|
-
rules: {
|
|
26
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
27
|
-
'@typescript-eslint/prefer-as-const': 'off',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
])
|