├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── jsconfig.json
├── media
└── demo.gif
├── package-lock.json
├── package.json
├── src
└── index.js
└── templates
├── base
├── README.md
├── _gitignore
├── index.html
├── jsconfig.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── assets
│ │ └── preact.svg
│ ├── index.jsx
│ └── style.css
└── vite.config.js
└── config
├── prerender-router
├── README.md
├── src
│ └── index.jsx
└── vite.config.js
├── prerender
├── README.md
├── src
│ └── index.jsx
└── vite.config.js
└── router
├── components
└── Header.jsx
├── index.jsx
├── pages
├── Home
│ ├── index.jsx
│ └── style.css
└── _404.jsx
└── style.css
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [{.*rc,*.yml}]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [package.json]
15 | insert_final_newline = false
16 |
17 | [*.md]
18 | trim_trailing_whitespace = false
19 |
20 | [test/fixtures/**/*.expected.*]
21 | trim_trailing_whitespace = false
22 | insert_final_newline = false
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .vim
26 |
27 | # Local Netlify folder
28 | .netlify
29 |
30 | ./templates/**/package-lock.json
31 | ./templates/**/yarn.lock
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 The Preact Authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # create-preact
2 |
3 | Create a Vite-powered Preact app in seconds
4 |
5 |
6 |
7 |
8 |
9 | ## Usage
10 |
11 | ```sh
12 | $ npm init preact
13 |
14 | $ yarn create preact
15 |
16 | $ pnpm create preact
17 | ```
18 |
19 | ## License
20 |
21 | [MIT](https://github.com/preactjs/create-preact/blob/master/LICENSE)
22 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "NodeNext",
6 | "allowJs": true,
7 | "checkJs": true,
8 | "resolveJsonModule": true,
9 | "noEmit": true,
10 | "jsx": "react-jsx",
11 | "jsxImportSource": "preact"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/media/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preactjs/create-preact/ce337fe5351014b484284c20621c9bfad5973479/media/demo.gif
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-preact",
3 | "version": "0.5.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "create-preact",
9 | "version": "0.5.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "@clack/prompts": "^0.9.0",
13 | "kolorist": "^1.8.0",
14 | "tinyexec": "^0.3.1"
15 | },
16 | "bin": {
17 | "create-preact": "src/index.js"
18 | },
19 | "devDependencies": {
20 | "@types/node": "^20.2.3",
21 | "preact": "^10.15.0",
22 | "prettier": "^2.6.2",
23 | "prettier-config-rschristian": "^0.1.1",
24 | "typescript": "^5.0.0"
25 | }
26 | },
27 | "node_modules/@clack/core": {
28 | "version": "0.4.0",
29 | "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.0.tgz",
30 | "integrity": "sha512-YJCYBsyJfNDaTbvDUVSJ3SgSuPrcujarRgkJ5NLjexDZKvaOiVVJvAQYx8lIgG0qRT8ff0fPgqyBCVivanIZ+A==",
31 | "license": "MIT",
32 | "dependencies": {
33 | "picocolors": "^1.0.0",
34 | "sisteransi": "^1.0.5"
35 | }
36 | },
37 | "node_modules/@clack/prompts": {
38 | "version": "0.9.0",
39 | "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.0.tgz",
40 | "integrity": "sha512-nGsytiExgUr4FL0pR/LeqxA28nz3E0cW7eLTSh3Iod9TGrbBt8Y7BHbV3mmkNC4G0evdYyQ3ZsbiBkk7ektArA==",
41 | "license": "MIT",
42 | "dependencies": {
43 | "@clack/core": "0.4.0",
44 | "picocolors": "^1.0.0",
45 | "sisteransi": "^1.0.5"
46 | }
47 | },
48 | "node_modules/@types/node": {
49 | "version": "20.2.3",
50 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz",
51 | "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==",
52 | "dev": true,
53 | "license": "MIT"
54 | },
55 | "node_modules/kolorist": {
56 | "version": "1.8.0",
57 | "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
58 | "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
59 | "license": "MIT"
60 | },
61 | "node_modules/picocolors": {
62 | "version": "1.1.1",
63 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
64 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
65 | "license": "ISC"
66 | },
67 | "node_modules/preact": {
68 | "version": "10.15.0",
69 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.0.tgz",
70 | "integrity": "sha512-nZSa8M2R2m1n7nJSBlzDpxRJaIsejrTO1vlFbdpFvyC8qM1iU+On2y0otfoUm6SRB5o0lF0CKDFxg6grEFU0iQ==",
71 | "dev": true,
72 | "license": "MIT",
73 | "funding": {
74 | "type": "opencollective",
75 | "url": "https://opencollective.com/preact"
76 | }
77 | },
78 | "node_modules/prettier": {
79 | "version": "2.8.8",
80 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
81 | "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
82 | "dev": true,
83 | "license": "MIT",
84 | "bin": {
85 | "prettier": "bin-prettier.js"
86 | },
87 | "engines": {
88 | "node": ">=10.13.0"
89 | },
90 | "funding": {
91 | "url": "https://github.com/prettier/prettier?sponsor=1"
92 | }
93 | },
94 | "node_modules/prettier-config-rschristian": {
95 | "version": "0.1.1",
96 | "resolved": "https://registry.npmjs.org/prettier-config-rschristian/-/prettier-config-rschristian-0.1.1.tgz",
97 | "integrity": "sha512-PRs3vZn+1PTGTTtDfdDZdrhzLhQCu4Qe5HxWo1R3Ui2R8X2PX5rKEg7NOfZbvjla0686+nYocV3mDOhMz2D4WQ==",
98 | "dev": true,
99 | "license": "MIT",
100 | "peerDependencies": {
101 | "prettier": "2.x"
102 | }
103 | },
104 | "node_modules/sisteransi": {
105 | "version": "1.0.5",
106 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
107 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
108 | "license": "MIT"
109 | },
110 | "node_modules/tinyexec": {
111 | "version": "0.3.1",
112 | "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
113 | "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
114 | "license": "MIT"
115 | },
116 | "node_modules/typescript": {
117 | "version": "5.0.4",
118 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
119 | "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
120 | "dev": true,
121 | "license": "Apache-2.0",
122 | "bin": {
123 | "tsc": "bin/tsc",
124 | "tsserver": "bin/tsserver"
125 | },
126 | "engines": {
127 | "node": ">=12.20"
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-preact",
3 | "version": "0.5.1",
4 | "description": "Create a Vite-powered Preact app in seconds",
5 | "type": "module",
6 | "bin": {
7 | "create-preact": "src/index.js"
8 | },
9 | "scripts": {
10 | "format": "prettier --write --ignore-path .gitignore ."
11 | },
12 | "authors": "The Preact Authors (https://preactjs.com)",
13 | "license": "MIT",
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/preactjs/create-preact.git"
17 | },
18 | "files": [
19 | "src",
20 | "templates",
21 | "LICENSE",
22 | "package.json",
23 | "README.md"
24 | ],
25 | "dependencies": {
26 | "@clack/prompts": "^0.9.0",
27 | "kolorist": "^1.8.0",
28 | "tinyexec": "^0.3.1"
29 | },
30 | "devDependencies": {
31 | "@types/node": "^20.2.3",
32 | "preact": "^10.15.0",
33 | "prettier": "^2.6.2",
34 | "prettier-config-rschristian": "^0.1.1",
35 | "typescript": "^5.0.0"
36 | },
37 | "prettier": "prettier-config-rschristian"
38 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { promises as fs, existsSync } from 'node:fs';
3 | import { dirname, resolve } from 'node:path';
4 | import { fileURLToPath } from 'node:url';
5 | import * as prompts from '@clack/prompts';
6 | import { x } from 'tinyexec';
7 | import * as kl from 'kolorist';
8 |
9 | const s = prompts.spinner();
10 | const brandColor = /** @type {const} */ ([174, 128, 255]);
11 |
12 | (async function createPreact() {
13 | const args = process.argv.slice(2);
14 |
15 | // Silences the 'Getting Started' info, mainly
16 | // for use in other initializers that may wrap this
17 | // one but provide their own scripts/instructions.
18 | const skipHint = args.includes('--skip-hints');
19 | const argDir = args.find((arg) => !arg.startsWith('--'));
20 | const packageManager = getPkgManager();
21 |
22 | prompts.intro(
23 | kl.trueColor(...brandColor)(
24 | 'Preact - Fast 3kB alternative to React with the same modern API',
25 | ),
26 | );
27 |
28 | const { dir, language, useRouter, usePrerender, useESLint } = await prompts.group(
29 | {
30 | dir: () =>
31 | argDir
32 | ? Promise.resolve(argDir)
33 | : prompts.text({
34 | message: 'Project directory:',
35 | placeholder: 'my-preact-app',
36 | validate(value) {
37 | if (value.length == 0) {
38 | return 'Directory name is required!';
39 | } else if (existsSync(value)) {
40 | return 'Refusing to overwrite existing directory or file! Please provide a non-clashing name.';
41 | }
42 | },
43 | }),
44 | language: () =>
45 | prompts.select({
46 | message: 'Project language:',
47 | initialValue: 'js',
48 | options: [
49 | { value: 'js', label: 'JavaScript' },
50 | { value: 'ts', label: 'TypeScript' },
51 | ],
52 | }),
53 | useRouter: () =>
54 | prompts.confirm({
55 | message: 'Use router?',
56 | initialValue: false,
57 | }),
58 | usePrerender: () =>
59 | prompts.confirm({
60 | message: 'Prerender app (SSG)?',
61 | initialValue: false,
62 | }),
63 | useESLint: () =>
64 | prompts.confirm({
65 | message: 'Use ESLint?',
66 | initialValue: false,
67 | }),
68 | },
69 | {
70 | onCancel: () => {
71 | prompts.cancel(kl.yellow('Cancelled'));
72 | process.exit(0);
73 | },
74 | },
75 | );
76 | const targetDir = resolve(process.cwd(), dir);
77 | const useTS = language === 'ts';
78 | /** @type {ConfigOptions} */
79 | const opts = { packageManager, useTS, useRouter, usePrerender, useESLint };
80 |
81 | await useSpinner(
82 | 'Setting up your project directory...',
83 | () => scaffold(targetDir, opts),
84 | 'Set up project directory',
85 | );
86 |
87 | await useSpinner(
88 | 'Installing project dependencies...',
89 | () => installDeps(targetDir, opts),
90 | 'Installed project dependencies',
91 | );
92 |
93 | if (!skipHint) {
94 | const gettingStarted = `
95 | ${kl.dim('$')} ${kl.lightBlue(`cd ${dir}`)}
96 | ${kl.dim('$')} ${kl.lightBlue(`${packageManager == 'npm' ? 'npm run' : packageManager} dev`)}
97 | `;
98 | prompts.note(gettingStarted.trim().replace(/^\t\t\t/gm, ''), 'Getting Started');
99 | }
100 |
101 | prompts.outro(kl.green(`You're all set!`));
102 | })();
103 |
104 | /**
105 | * @param {string} startMessage
106 | * @param {() => Promise} fn
107 | * @param {string} finishMessage
108 | */
109 | async function useSpinner(startMessage, fn, finishMessage) {
110 | s.start(startMessage);
111 | await fn();
112 | s.stop(kl.green(finishMessage));
113 | }
114 |
115 | /**
116 | * @typedef {Object} ConfigOptions
117 | * @property {'yarn' | 'pnpm' | 'npm'} packageManager
118 | * @property {boolean} useTS
119 | * @property {boolean} useRouter
120 | * @property {boolean} usePrerender
121 | * @property {boolean} useESLint
122 | */
123 |
124 | /**
125 | * Copy template files to user's chosen directory
126 | *
127 | * @param {string} to
128 | * @param {ConfigOptions} opts
129 | */
130 | async function scaffold(to, opts) {
131 | await fs.mkdir(to, { recursive: true });
132 |
133 | const __dirname = dirname(fileURLToPath(import.meta.url));
134 | await templateDir(resolve(__dirname, '../templates', 'base'), to, opts);
135 |
136 | if (opts.useRouter) {
137 | await templateDir(
138 | resolve(__dirname, '../templates', 'config', 'router'),
139 | resolve(to, 'src'),
140 | opts,
141 | );
142 | }
143 |
144 | if (opts.usePrerender) {
145 | await templateDir(
146 | resolve(
147 | __dirname,
148 | '../templates',
149 | 'config',
150 | opts.useRouter ? 'prerender-router' : 'prerender',
151 | ),
152 | to,
153 | opts,
154 | );
155 |
156 | const htmlPath = resolve(to, 'index.html');
157 | const html = (await fs.readFile(htmlPath, 'utf-8')).replace('
13 |