├── .gitignore ├── README.md ├── _gulp ├── combineTasks.ts ├── gulpSetup.ts ├── logo.png ├── modules │ ├── buildEnvironment.ts │ ├── fileSetup.ts │ ├── gulpfileGenerator.ts │ └── setupQuestions.ts ├── tasks │ ├── cleanTask.ts │ ├── imagesTask.ts │ ├── markupTask.ts │ ├── scriptTask.ts │ └── styleTask.ts ├── types │ └── index.ts ├── user-choices.json ├── utils │ ├── fileSystem.ts │ └── logger.ts └── vendors │ ├── normalize.sass │ ├── normalize.scss │ ├── reset.sass │ └── reset.scss ├── gulpfile.js ├── package.json ├── tsconfig.json └── typings └── custon.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn-error.log 3 | yarn.lock 4 | package-lock.json 5 | npm-debug.log 6 | 7 | .DS_Store 8 | .env 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | .pnp 14 | .pnp.js 15 | .pnp.cjs 16 | *.pnp.js 17 | *.pnp.cjs 18 | .cache/ 19 | 20 | build/ 21 | temp/ 22 | *.gitignore 23 | .vscode 24 | 25 | # cache files for sublime text 26 | *.tmlanguage.cache 27 | *.tmPreferences.cache 28 | *.stTheme.cache 29 | 30 | # sftp configuration file 31 | sftp-config.json 32 | 33 | # Package control specific files 34 | Package Control.last-run 35 | Package Control.ca-list 36 | Package Control.ca-bundle 37 | Package Control.system-ca-bundle 38 | Package Control.cache/ 39 | Package Control.ca-certs/ 40 | bh_unicode_properties.cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Learning Kit Generator 2 | 3 | | Web Learning Kit Generator is a tool designed for beginners who want to build static websites with minimal configuration. The application creates a Gulp build process dynamically based on the user's choice of markup language (HTML/Pug), stylesheet language (SASS/SCSS), and scripting language (Javascript/Typescript). It's an easy and fast way to get started with a more realistic development environment on your local machine, beyond online IDEs. | ![Logo](./_gulp/logo.png) | 4 | |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------:| 5 | 6 | 7 | This project is based on a simple and fast workflow focused mainly on the front-end task. It gives a solid starting point for newcomers who wants a ready-to-deploy local environment setup. The sources used to build this project includes: 8 | 9 | * [H5BP Project](https://github.com/h5bp/html5-boilerplate) 10 | * [React Redux Starter Kit](https://github.com/davezuko/react-redux-starter-kit) 11 | * [Mark Goodyear's Blog](https://markgoodyear.com/2014/01/getting-started-with-gulp/) 12 | * [Web Starter Kit](https://github.com/google/web-starter-kit) 13 | 14 | ## Features 15 | 16 | * Dynamic Configuration: Choose your preferred markup (**Pug/HTML**), stylesheet (**Sass/SCSS/CSS**), and script (**JavaScript/TypeScript**) languages, and the tool will generate a customized Gulpfile for you. 17 | 18 | * Beginner-Friendly: Ideal for those new to web development who want to experiment with real-world tools and workflows. 19 | 20 | * Extensible: Start simple, and gradually explore more advanced features as you become comfortable. 21 | 22 | * Minimal Setup: Get up and running quickly without the need for complex configuration. 23 | 24 | * Realistic Environment: Experience coding outside of online IDEs, and start using Git or other tools as you grow your skills. 25 | 26 | * Bonus: you can add **Josh Comeau** css reset and **Necolas** css normalize 27 | 28 | ## Prerequisites 29 | 30 | Before you begin, ensure you have the following installed on your system: 31 | 32 | * Node.js (v14 or later) 33 | * **npm** (comes with Node.js) or **Yarn** 34 | 35 | ## Getting Started 36 | 37 | ### 1 - Clone or download this repository 38 | 39 | ### 2 - Install dependencies: 40 | 41 | Using npm: 42 | ```bash 43 | npm install 44 | ``` 45 | 46 | Or using Yarn: 47 | ```bash 48 | yarn install 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### Development 54 | 55 | To start the development server with live reloading: 56 | 57 | ```bash 58 | yarn start 59 | ``` 60 | 61 | Or 62 | ```bash 63 | npm start 64 | ``` 65 | 66 | This will run the `gulp` command, which starts a local server and watches for file changes. 67 | 68 | ### Production Build 69 | 70 | To create a production-ready build: 71 | 72 | ```bash 73 | yarn build 74 | ``` 75 | 76 | Or 77 | ```bash 78 | npm build 79 | ``` 80 | 81 | This will generate optimized files in the `dist` directory. 82 | 83 | ### Running Your Local Server With Gulp 84 | 85 | This task will open the browser window usually with the URL http://localhost:3000/. Any saved changes made to the project files, will reflect automatically over the browser. 86 | 87 | ## Project Structure 88 | 89 | ``` 90 | . 91 | ├── src/ # ** Your code folder! ** 92 | │ ├── img/ # Image files 93 | │ ├── js/ # JS/TS files 94 | │ ├── styles/ # Sass/SCSS files 95 | │ └── templates/ # Pug/HTML files 96 | │ 97 | ├── _gulp/ # Gulp configuration and tasks 98 | ├── dist/ # Production build output 99 | ├── gulpfile.js # Gulp entry point 100 | ├── package.json # Project dependencies and scripts 101 | └── README.md # Project documentation 102 | ``` 103 | The src/ directory is created after the Yarn or npm install, **this is where your code journey begins**. The dist/ and build/ folders can be used to host your web project in a simple and convenient way. there are a lot of options to host it, including **Github Pages** 104 | 105 | ## Contributing 106 | 107 | Contributions are welcome! Please feel free to submit a Pull Request. 108 | 109 | ## License 110 | 111 | This project is open source and available under the [MIT License](LICENSE). 112 | 113 | --- 114 | 115 | For more detailed information about the gulp tasks and project configuration, please refer to the comments in the `gulpfile.js` and the files in the `_gulp` directory. 116 | -------------------------------------------------------------------------------- /_gulp/combineTasks.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from 'fs/promises'; 2 | import { parallel, series, TaskFunction } from 'gulp'; 3 | import { browserSyncServe, createWatchTask } from './modules/buildEnvironment'; 4 | import { copyVendorCSS, createProjectFiles, createProjectStructure } from './modules/fileSetup'; 5 | import { confirmProjectDeletion, promptUser } from './modules/setupQuestions'; 6 | import { UserChoices } from './types'; 7 | import { deleteDirectory, fileExists } from './utils/fileSystem'; 8 | import { logger } from './utils/logger'; 9 | 10 | async function loadUserChoices(): Promise { 11 | try { 12 | const data = await readFile('_gulp/user-choices.json', 'utf8'); 13 | return JSON.parse(data); 14 | } catch (error) { 15 | logger.error('Failed to load user choices. Please run setup again.'); 16 | process.exit(1); 17 | } 18 | } 19 | 20 | async function createGulpTasks() { 21 | const choices = await loadUserChoices(); 22 | 23 | try { 24 | const { styleTask } = await import('./tasks/styleTask'); 25 | const { scriptTask } = await import('./tasks/scriptTask'); 26 | const { markupTask } = await import('./tasks/markupTask'); 27 | const { imagesTask } = await import('./tasks/imagesTask'); 28 | const { cleanTask } = await import('./tasks/cleanTask'); 29 | 30 | const watchTask = createWatchTask(choices, { 31 | styleTask: styleTask(choices) as TaskFunction, 32 | scriptTask: scriptTask(choices) as TaskFunction, 33 | markupTask: markupTask(choices) as TaskFunction, 34 | imagesTask: imagesTask() as TaskFunction 35 | }); 36 | 37 | const defaultTask: TaskFunction = (done) => { 38 | return series( 39 | cleanTask(), 40 | parallel( 41 | styleTask(choices), 42 | scriptTask(choices), 43 | markupTask(choices), 44 | imagesTask() 45 | ), 46 | browserSyncServe, 47 | watchTask 48 | )(done); 49 | }; 50 | 51 | const buildTask: TaskFunction = (done) => { 52 | return series( 53 | cleanTask(), 54 | parallel( 55 | styleTask(choices), 56 | scriptTask(choices), 57 | markupTask(choices), 58 | imagesTask() 59 | ) 60 | )(done); 61 | }; 62 | 63 | return { defaultTask, buildTask }; 64 | } catch (error) { 65 | logger.error(`Failed to create Gulp tasks: ${(error as Error).message}`); 66 | process.exit(1); 67 | } 68 | } 69 | 70 | export async function setup(): Promise { 71 | try { 72 | const projectExists = fileExists('src') || fileExists('dist'); 73 | if (projectExists) { 74 | const shouldDelete = await confirmProjectDeletion(); 75 | if (!shouldDelete) { 76 | logger.info('Project setup canceled. Exiting...'); 77 | return; 78 | } 79 | deleteDirectory('src'); 80 | deleteDirectory('dist'); 81 | } 82 | 83 | const choices: UserChoices = await promptUser(); 84 | 85 | await writeFile('_gulp/user-choices.json', JSON.stringify(choices, null, 2)); 86 | 87 | createProjectStructure(choices); 88 | createProjectFiles(choices); 89 | copyVendorCSS(choices); 90 | 91 | logger.success('Setup complete. Gulpfile has been generated.'); 92 | logger.info('Starting development server...'); 93 | 94 | const { defaultTask } = await createGulpTasks(); 95 | defaultTask((err?: Error | null) => { 96 | if (err) { 97 | logger.error(`Development server failed: ${err.message}`); 98 | } else { 99 | logger.success('Development server started'); 100 | } 101 | }); 102 | } catch (error: unknown) { 103 | logger.error(`An error occurred during setup: ${(error as Error).message}`); 104 | } 105 | } 106 | 107 | setup().catch(error => { 108 | logger.error(`An error occurred: ${(error as Error).message}`); 109 | process.exit(1); 110 | }); -------------------------------------------------------------------------------- /_gulp/gulpSetup.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { writeFile } from 'fs/promises'; 3 | import { copyVendorCSS, createProjectFiles, createProjectStructure } from './modules/fileSetup'; 4 | import { generateGulpfile } from './modules/gulpfileGenerator'; 5 | import { confirmProjectDeletion, promptUser } from './modules/setupQuestions'; 6 | import { UserChoices } from './types'; 7 | import { deleteDirectory, fileExists } from './utils/fileSystem'; 8 | import { logger } from './utils/logger'; 9 | 10 | async function setup(): Promise { 11 | try { 12 | const projectExists = fileExists('src') || fileExists('dist'); 13 | if (projectExists) { 14 | const shouldDelete = await confirmProjectDeletion(); 15 | if (!shouldDelete) { 16 | logger.info('Project setup canceled. Exiting...'); 17 | return; 18 | } 19 | deleteDirectory('src'); 20 | deleteDirectory('dist'); 21 | } 22 | 23 | const choices: UserChoices = await promptUser(); 24 | 25 | await writeFile('_gulp/user-choices.json', JSON.stringify(choices, null, 2)); 26 | 27 | createProjectStructure(choices); 28 | createProjectFiles(choices); 29 | copyVendorCSS(choices); 30 | generateGulpfile(choices); 31 | 32 | logger.success('Setup complete. Gulpfile has been generated.'); 33 | logger.info('Starting development server...'); 34 | 35 | exec('yarn start', (error, stdout, stderr) => { 36 | if (error) { 37 | logger.error(`Error: ${error.message}`); 38 | return; 39 | } 40 | if (stderr) { 41 | logger.error(`Stderr: ${stderr}`); 42 | return; 43 | } 44 | console.log(stdout); 45 | }); 46 | } catch (error: unknown) { 47 | logger.error(`An error occurred during setup: ${(error as Error).message}`); 48 | } 49 | } 50 | 51 | setup(); 52 | -------------------------------------------------------------------------------- /_gulp/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Errec/web-learning-kit-generator/5fc09afa4d8a70354ed779c1acf77ba2efb464be/_gulp/logo.png -------------------------------------------------------------------------------- /_gulp/modules/buildEnvironment.ts: -------------------------------------------------------------------------------- 1 | import { create as createBrowserSync } from 'browser-sync'; 2 | import { series, TaskFunction, watch } from 'gulp'; 3 | import { UserChoices } from '../types'; 4 | 5 | const bs = createBrowserSync(); 6 | 7 | export function browserSyncServe(cb: () => void): void { 8 | bs.init({ 9 | server: { baseDir: 'dist/' }, 10 | open: false, 11 | notify: false 12 | }); 13 | cb(); 14 | } 15 | 16 | export function browserSyncReload(cb: () => void): void { 17 | bs.reload(); 18 | cb(); 19 | } 20 | 21 | interface Tasks { 22 | styleTask: TaskFunction; 23 | scriptTask: TaskFunction; 24 | markupTask: TaskFunction; 25 | imagesTask: TaskFunction; 26 | } 27 | 28 | export function createWatchTask(choices: UserChoices, tasks: Tasks) { 29 | return function watchTask(): void { 30 | const stylePath = `src/${choices.style === 'Sass' ? 'sass' : 'scss'}/**/*.${choices.style === 'Sass' ? 'sass' : 'scss'}`; 31 | const scriptPath = `src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}`; 32 | const markupPath = `src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}`; 33 | const imgPath = 'src/img/**/*'; 34 | 35 | watch(stylePath, series(tasks.styleTask, browserSyncReload)); 36 | watch(scriptPath, series(tasks.scriptTask, browserSyncReload)); 37 | watch(markupPath, series(tasks.markupTask, browserSyncReload)); 38 | watch(imgPath, series(tasks.imagesTask, browserSyncReload)); 39 | }; 40 | } -------------------------------------------------------------------------------- /_gulp/modules/fileSetup.ts: -------------------------------------------------------------------------------- 1 | import { UserChoices } from '../types'; 2 | import { copyFile, createDirectory, writeFile } from '../utils/fileSystem'; 3 | 4 | export function createProjectStructure(choices: UserChoices): void { 5 | const dirs = [ 6 | 'src', 7 | `src/${choices.script === 'JavaScript' ? 'js' : 'ts'}`, 8 | `src/${choices.style === 'Sass' ? 'sass/base' : 'scss/base'}`, // Updated path for base 9 | `src/${choices.markup === 'HTML' ? 'html' : 'pug'}`, 10 | 'src/img', 11 | 'dist/css' 12 | ]; 13 | 14 | dirs.forEach(createDirectory); 15 | } 16 | 17 | export function createProjectFiles(choices: UserChoices): void { 18 | const scriptExt = choices.script === 'JavaScript' ? 'js' : 'ts'; 19 | const styleExt = choices.style === 'Sass' ? 'sass' : 'scss'; 20 | 21 | const scriptContent = choices.script === 'JavaScript' 22 | ? 'console.log("Hello, World!");' 23 | : 'console.log("Hello, TypeScript!");'; 24 | writeFile(`src/${scriptExt}/main.${scriptExt}`, scriptContent); 25 | 26 | let styleContent = `// Main ${choices.style} file\n`; 27 | if (choices.addNormalize) styleContent += choices.style === 'Sass' ? "@import 'base/normalize'\n" : "@import 'base/normalize';\n"; 28 | if (choices.addReset) styleContent += choices.style === 'Sass' ? "@import 'base/reset'\n" : "@import 'base/reset';\n"; 29 | writeFile(`src/${styleExt}/main.${styleExt}`, styleContent); 30 | 31 | const markupContent = choices.markup === 'HTML' ? getHtmlTemplate() : getPugTemplate(); 32 | const markupFilePath = choices.markup === 'HTML' ? 'src/html/index.html' : 'src/pug/index.pug'; 33 | 34 | writeFile(markupFilePath, markupContent); 35 | writeFile('src/favicon.ico', ''); 36 | } 37 | 38 | export function copyVendorCSS(choices: UserChoices): void { 39 | const vendorFiles = [ 40 | { type: 'Normalize', src: `_gulp/vendors/normalize.${choices.style === 'Sass' ? 'sass' : 'scss'}` }, 41 | { type: 'Reset', src: `_gulp/vendors/reset.${choices.style === 'Sass' ? 'sass' : 'scss'}` } 42 | ]; 43 | 44 | vendorFiles.forEach(file => { 45 | if (choices[`add${file.type}` as keyof UserChoices]) { 46 | const dest = `src/${choices.style === 'Sass' ? 'sass/base' : 'scss/base'}/_${file.type.toLowerCase()}.${choices.style === 'Sass' ? 'sass' : 'scss'}`; // Save in base folder 47 | copyFile(file.src, dest); 48 | } 49 | }); 50 | } 51 | 52 | function getHtmlTemplate(): string { 53 | return ` 54 | 55 | 56 | 57 | 58 | 59 | HTML-Sass Boilerplate 60 | 61 | 62 | 63 | 64 |

Hello, World!

65 | 66 | 67 | `; 68 | } 69 | 70 | function getPugTemplate(): string { 71 | return ` 72 | doctype html 73 | html(lang="en") 74 | head 75 | meta(charset="UTF-8") 76 | meta(name="viewport" content="width=device-width, initial-scale=1.0") 77 | title Pug-Sass Boilerplate 78 | link(rel="stylesheet" href="css/main.css") 79 | link(rel="icon" href="favicon.ico" type="image/x-icon") 80 | body 81 | h1 Hello, World! 82 | script(src="js/main.js")`; 83 | } 84 | -------------------------------------------------------------------------------- /_gulp/modules/gulpfileGenerator.ts: -------------------------------------------------------------------------------- 1 | import { UserChoices } from '../types'; 2 | import { writeFile } from '../utils/fileSystem'; 3 | 4 | export function generateGulpfile(choices: UserChoices): void { 5 | const gulpfileContent = ` 6 | const { src, dest, watch, series, parallel } = require('gulp'); 7 | const sass = require('gulp-sass')(require('sass')); 8 | const autoprefixer = require('gulp-autoprefixer'); 9 | const cleanCSS = require('gulp-clean-css'); 10 | const browserify = require('browserify'); 11 | const babelify = require('babelify'); 12 | const source = require('vinyl-source-stream'); 13 | const buffer = require('vinyl-buffer'); 14 | const uglify = require('gulp-uglify'); 15 | const rename = require('gulp-rename'); 16 | const browserSync = require('browser-sync').create(); 17 | const imagemin = require('gulp-imagemin'); 18 | const del = require('del'); 19 | const plumber = require('gulp-plumber'); 20 | const sourcemaps = require('gulp-sourcemaps'); 21 | const gulpif = require('gulp-if'); 22 | const pug = ${choices.markup === 'Pug' ? "require('gulp-pug')" : 'null'}; 23 | const typescript = ${choices.script === 'TypeScript' ? "require('gulp-typescript')" : 'null'}; 24 | const tsify = ${choices.script === 'TypeScript' ? "require('tsify')" : 'null'}; 25 | 26 | const production = process.env.NODE_ENV === 'production'; 27 | 28 | async function clean() { 29 | await del(['dist']); 30 | } 31 | 32 | function styles() { 33 | return src('src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}') 34 | .pipe(plumber()) 35 | .pipe(gulpif(!production, sourcemaps.init())) 36 | .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) 37 | .pipe(autoprefixer()) 38 | .pipe(cleanCSS()) 39 | .pipe(gulpif(!production, sourcemaps.write('.'))) 40 | .pipe(dest('dist/css')) 41 | .pipe(browserSync.stream()); 42 | } 43 | 44 | function scripts() { 45 | const b = browserify({ 46 | entries: 'src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/main.${choices.script === 'TypeScript' ? 'ts' : 'js'}', 47 | debug: !production, 48 | }) 49 | .transform(babelify, { 50 | presets: ['@babel/preset-env'], 51 | extensions: ['.js', '.ts'] 52 | }); 53 | 54 | ${choices.script === 'TypeScript' ? 'b.plugin(tsify);' : ''} 55 | 56 | return b.bundle() 57 | .pipe(source('main.js')) 58 | .pipe(buffer()) 59 | .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) 60 | .pipe(dest('dist/js')) 61 | .pipe(uglify()) 62 | .pipe(rename('main.min.js')) 63 | .pipe(gulpif(!production, sourcemaps.write('.'))) 64 | .pipe(dest('dist/js')); 65 | } 66 | 67 | function markup() { 68 | return src('src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}') 69 | .pipe(plumber()) 70 | ${choices.markup === 'Pug' ? '.pipe(pug())' : ''} 71 | .pipe(dest('dist')); 72 | } 73 | 74 | function images() { 75 | return src('src/img/**/*') 76 | .pipe(imagemin()) 77 | .pipe(dest('dist/img')); 78 | } 79 | 80 | function serve(cb) { 81 | browserSync.init({ 82 | server: { 83 | baseDir: './dist' 84 | }, 85 | open: true 86 | }); 87 | cb(); 88 | } 89 | 90 | function watchFiles(cb) { 91 | watch('src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}', styles); 92 | watch('src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}', series(scripts, reload)); 93 | watch('src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}', series(markup, reload)); 94 | watch('src/img/**/*', series(images, reload)); 95 | cb(); 96 | } 97 | 98 | function reload(cb) { 99 | browserSync.reload(); 100 | cb(); 101 | } 102 | 103 | exports.clean = clean; 104 | exports.styles = styles; 105 | exports.scripts = scripts; 106 | exports.markup = markup; 107 | exports.images = images; 108 | exports.watch = watchFiles; 109 | 110 | exports.build = series(clean, parallel(styles, scripts, markup, images)); 111 | exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); 112 | `; 113 | 114 | writeFile('gulpfile.js', gulpfileContent); 115 | } -------------------------------------------------------------------------------- /_gulp/modules/setupQuestions.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { UserChoices } from '../types'; 3 | 4 | export const questions = [ 5 | { type: 'list', name: 'script', message: 'Choose JavaScript or TypeScript:', choices: ['JavaScript', 'TypeScript'] }, 6 | { type: 'list', name: 'style', message: 'Choose Sass or SCSS:', choices: ['Sass', 'SCSS'] }, 7 | { type: 'list', name: 'markup', message: 'Choose HTML or Pug:', choices: ['HTML', 'Pug'] }, 8 | { type: 'confirm', name: 'addNormalize', message: 'Add normalize.css?', default: false }, 9 | { type: 'confirm', name: 'addReset', message: 'Add reset.css?', default: false } 10 | ]; 11 | 12 | export async function promptUser(): Promise { 13 | return inquirer.prompt(questions); 14 | } 15 | 16 | export async function confirmProjectDeletion(): Promise { 17 | const { deleteProject } = await inquirer.prompt([ 18 | { type: 'confirm', name: 'deleteProject', message: 'DELETE existing project and start new?', default: false } 19 | ]); 20 | return deleteProject; 21 | } -------------------------------------------------------------------------------- /_gulp/tasks/cleanTask.ts: -------------------------------------------------------------------------------- 1 | import del from 'del'; 2 | 3 | export function cleanTask() { 4 | return function(cb: (error?: Error | null) => void) { 5 | del(['dist']).then(() => cb()).catch(cb); 6 | }; 7 | } -------------------------------------------------------------------------------- /_gulp/tasks/imagesTask.ts: -------------------------------------------------------------------------------- 1 | import { dest, src, TaskFunction } from 'gulp'; 2 | import imagemin from 'gulp-imagemin'; 3 | 4 | export function imagesTask(): TaskFunction { 5 | return function() { 6 | return src('src/img/**/*') 7 | .pipe(imagemin()) 8 | .pipe(dest('dist/img')); 9 | }; 10 | } -------------------------------------------------------------------------------- /_gulp/tasks/markupTask.ts: -------------------------------------------------------------------------------- 1 | import { dest, src } from 'gulp'; 2 | import plumber from 'gulp-plumber'; 3 | import pug from 'gulp-pug'; 4 | import { UserChoices } from '../types'; 5 | 6 | export function markupTask(choices: UserChoices) { 7 | return function() { 8 | return src(`src/${choices.markup.toLowerCase()}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}`) 9 | .pipe(plumber()) 10 | .pipe(choices.markup === 'Pug' ? pug() : plumber()) 11 | .pipe(dest('dist')); 12 | }; 13 | } -------------------------------------------------------------------------------- /_gulp/tasks/scriptTask.ts: -------------------------------------------------------------------------------- 1 | import browserify from 'browserify'; 2 | import { dest, TaskFunction } from 'gulp'; 3 | import rename from 'gulp-rename'; 4 | import sourcemaps from 'gulp-sourcemaps'; 5 | import uglify from 'gulp-uglify'; 6 | import tsify from 'tsify'; 7 | import buffer from 'vinyl-buffer'; 8 | import source from 'vinyl-source-stream'; 9 | import { UserChoices } from '../types'; 10 | 11 | export function scriptTask(choices: UserChoices): TaskFunction { 12 | return function() { 13 | const b = browserify({ 14 | entries: `src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/main.${choices.script === 'TypeScript' ? 'ts' : 'js'}`, 15 | debug: true, 16 | }); 17 | 18 | if (choices.script === 'TypeScript') { 19 | b.plugin(tsify); 20 | } 21 | 22 | return b.bundle() 23 | .pipe(source('main.js')) 24 | .pipe(buffer()) 25 | .pipe(sourcemaps.init({ loadMaps: true })) 26 | .pipe(dest('dist/js')) 27 | .pipe(uglify()) 28 | .pipe(rename('main.min.js')) 29 | .pipe(sourcemaps.write('./')) 30 | .pipe(dest('dist/js')); 31 | }; 32 | } -------------------------------------------------------------------------------- /_gulp/tasks/styleTask.ts: -------------------------------------------------------------------------------- 1 | import { dest, src, TaskFunction } from 'gulp'; 2 | import autoprefixer from 'gulp-autoprefixer'; 3 | import cleanCSS from 'gulp-clean-css'; 4 | import plumber from 'gulp-plumber'; 5 | import gulpSass from 'gulp-sass'; 6 | import sourcemaps from 'gulp-sourcemaps'; 7 | import * as sass from 'sass'; 8 | import { UserChoices } from '../types'; 9 | 10 | const sassCompiler = gulpSass(sass); 11 | 12 | export function styleTask(choices: UserChoices): TaskFunction { 13 | return function() { 14 | return src(`src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}`, { sourcemaps: true }) 15 | .pipe(plumber()) 16 | .pipe(sassCompiler({ indentedSyntax: choices.style === 'Sass' })) 17 | .pipe(autoprefixer()) 18 | .pipe(cleanCSS()) 19 | .pipe(sourcemaps.write('.')) 20 | .pipe(dest('dist/css')); 21 | }; 22 | } -------------------------------------------------------------------------------- /_gulp/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface UserChoices { 2 | script: 'JavaScript' | 'TypeScript'; 3 | style: 'Sass' | 'SCSS'; 4 | markup: 'HTML' | 'Pug'; 5 | addNormalize: boolean; 6 | addReset: boolean; 7 | } 8 | 9 | export interface GulpTasks { 10 | styleTask: string; 11 | scriptTask: string; 12 | markupTask: string; 13 | } -------------------------------------------------------------------------------- /_gulp/user-choices.json: -------------------------------------------------------------------------------- 1 | { 2 | "script": "TypeScript", 3 | "style": "Sass", 4 | "markup": "Pug", 5 | "addNormalize": true, 6 | "addReset": true 7 | } -------------------------------------------------------------------------------- /_gulp/utils/fileSystem.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | export function createDirectory(dir: string): void { 5 | if (!fs.existsSync(dir)) { 6 | fs.mkdirSync(dir, { recursive: true }); 7 | } 8 | } 9 | 10 | export function writeFile(filePath: string, content: string): void { 11 | fs.writeFileSync(filePath, content); 12 | } 13 | 14 | export function copyFile(src: string, dest: string): void { 15 | fs.copyFileSync(path.resolve(src), path.resolve(dest)); 16 | } 17 | 18 | export function fileExists(filePath: string): boolean { 19 | return fs.existsSync(filePath); 20 | } 21 | 22 | export function deleteDirectory(dir: string): void { 23 | if (fs.existsSync(dir)) { 24 | fs.rmSync(dir, { recursive: true, force: true }); 25 | } 26 | } -------------------------------------------------------------------------------- /_gulp/utils/logger.ts: -------------------------------------------------------------------------------- 1 | export const logger = { 2 | info: (message: string): void => console.log(`\x1b[36m[INFO]\x1b[0m ${message}`), 3 | success: (message: string): void => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${message}`), 4 | warning: (message: string): void => console.log(`\x1b[33m[WARNING]\x1b[0m ${message}`), 5 | error: (message: string): void => console.log(`\x1b[31m[ERROR]\x1b[0m ${message}`), 6 | }; -------------------------------------------------------------------------------- /_gulp/vendors/normalize.sass: -------------------------------------------------------------------------------- 1 | html 2 | line-height: 1.15 3 | -webkit-text-size-adjust: 100% 4 | body 5 | margin: 0 6 | main 7 | display: block 8 | h1 9 | font-size: 2em 10 | margin: 0.67em 0 11 | hr 12 | box-sizing: content-box 13 | height: 0 14 | overflow: visible 15 | pre 16 | font-family: monospace, monospace 17 | font-size: 1em 18 | a 19 | background-color: transparent 20 | abbr[title] 21 | border-bottom: none 22 | text-decoration: underline 23 | text-decoration: underline dotted 24 | b, 25 | strong 26 | font-weight: bolder 27 | code, 28 | kbd, 29 | samp 30 | font-family: monospace, monospace 31 | font-size: 1em 32 | small 33 | font-size: 80% 34 | sub, 35 | sup 36 | font-size: 75% 37 | line-height: 0 38 | position: relative 39 | vertical-align: baseline 40 | sub 41 | bottom: -0.25em 42 | sup 43 | top: -0.5em 44 | img 45 | border-style: none 46 | button, 47 | input, 48 | optgroup, 49 | select, 50 | textarea 51 | font-family: inherit 52 | font-size: 100% 53 | line-height: 1.15 54 | margin: 0 55 | button, 56 | input 57 | overflow: visible 58 | button, 59 | select 60 | text-transform: none 61 | button, 62 | [type="button"], 63 | [type="reset"], 64 | [type="submit"] 65 | -webkit-appearance: button 66 | appearance: button 67 | button::-moz-focus-inner, 68 | [type="button"]::-moz-focus-inner, 69 | [type="reset"]::-moz-focus-inner, 70 | [type="submit"]::-moz-focus-inner 71 | border-style: none 72 | padding: 0 73 | button:-moz-focusring, 74 | [type="button"]:-moz-focusring, 75 | [type="reset"]:-moz-focusring, 76 | [type="submit"]:-moz-focusring 77 | outline: 1px dotted ButtonText 78 | fieldset 79 | padding: 0.35em 0.75em 0.625em 80 | legend 81 | box-sizing: border-box 82 | color: inherit 83 | display: table 84 | max-width: 100% 85 | padding: 0 86 | white-space: normal 87 | progress 88 | vertical-align: baseline 89 | textarea 90 | overflow: auto 91 | [type="checkbox"], 92 | [type="radio"] 93 | box-sizing: border-box 94 | padding: 0 95 | [type="number"]::-webkit-inner-spin-button, 96 | [type="number"]::-webkit-outer-spin-button 97 | height: auto 98 | [type="search"] 99 | -webkit-appearance: textfield 100 | appearance: textfield 101 | outline-offset: -2px 102 | [type="search"]::-webkit-search-decoration 103 | -webkit-appearance: none 104 | ::-webkit-file-upload-button 105 | -webkit-appearance: button 106 | font: inherit 107 | details 108 | display: block 109 | summary 110 | display: list-item 111 | template 112 | display: none 113 | [hidden] 114 | display: none 115 | -------------------------------------------------------------------------------- /_gulp/vendors/normalize.scss: -------------------------------------------------------------------------------- 1 | html { 2 | line-height: 1.15; 3 | -webkit-text-size-adjust: 100%; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | } 9 | 10 | main { 11 | display: block; 12 | } 13 | 14 | h1 { 15 | font-size: 2em; 16 | margin: 0.67em 0; 17 | } 18 | 19 | hr { 20 | box-sizing: content-box; 21 | height: 0; 22 | overflow: visible; 23 | } 24 | 25 | pre { 26 | font-family: monospace, monospace; 27 | font-size: 1em; 28 | } 29 | 30 | a { 31 | background-color: transparent; 32 | } 33 | 34 | abbr[title] { 35 | border-bottom: none; 36 | text-decoration: underline; 37 | text-decoration: underline dotted; 38 | } 39 | 40 | b, 41 | strong { 42 | font-weight: bolder; 43 | } 44 | 45 | code, 46 | kbd, 47 | samp { 48 | font-family: monospace, monospace; 49 | font-size: 1em; 50 | } 51 | 52 | small { 53 | font-size: 80%; 54 | } 55 | 56 | sub, 57 | sup { 58 | font-size: 75%; 59 | line-height: 0; 60 | position: relative; 61 | vertical-align: baseline; 62 | } 63 | 64 | sub { 65 | bottom: -0.25em; 66 | } 67 | 68 | sup { 69 | top: -0.5em; 70 | } 71 | 72 | img { 73 | border-style: none; 74 | } 75 | 76 | button, 77 | input, 78 | optgroup, 79 | select, 80 | textarea { 81 | font-family: inherit; 82 | font-size: 100%; 83 | line-height: 1.15; 84 | margin: 0; 85 | } 86 | 87 | button, 88 | input { 89 | overflow: visible; 90 | } 91 | 92 | button, 93 | select { 94 | text-transform: none; 95 | } 96 | 97 | button, 98 | [type="button"], 99 | [type="reset"], 100 | [type="submit"] { 101 | -webkit-appearance: button; 102 | appearance: button; 103 | } 104 | 105 | button::-moz-focus-inner, 106 | [type="button"]::-moz-focus-inner, 107 | [type="reset"]::-moz-focus-inner, 108 | [type="submit"]::-moz-focus-inner { 109 | border-style: none; 110 | padding: 0; 111 | } 112 | 113 | button:-moz-focusring, 114 | [type="button"]:-moz-focusring, 115 | [type="reset"]:-moz-focusring, 116 | [type="submit"]:-moz-focusring { 117 | outline: 1px dotted ButtonText; 118 | } 119 | 120 | fieldset { 121 | padding: 0.35em 0.75em 0.625em; 122 | } 123 | 124 | legend { 125 | box-sizing: border-box; 126 | color: inherit; 127 | display: table; 128 | max-width: 100%; 129 | padding: 0; 130 | white-space: normal; 131 | } 132 | 133 | progress { 134 | vertical-align: baseline; 135 | } 136 | 137 | textarea { 138 | overflow: auto; 139 | } 140 | 141 | [type="checkbox"], 142 | [type="radio"] { 143 | box-sizing: border-box; 144 | padding: 0; 145 | } 146 | 147 | [type="number"]::-webkit-inner-spin-button, 148 | [type="number"]::-webkit-outer-spin-button { 149 | height: auto; 150 | } 151 | 152 | [type="search"] { 153 | -webkit-appearance: textfield; 154 | appearance: textfield; 155 | outline-offset: -2px; 156 | } 157 | 158 | [type="search"]::-webkit-search-decoration { 159 | -webkit-appearance: none; 160 | } 161 | 162 | ::-webkit-file-upload-button { 163 | -webkit-appearance: button; 164 | font: inherit; 165 | } 166 | 167 | details { 168 | display: block; 169 | } 170 | 171 | summary { 172 | display: list-item; 173 | } 174 | 175 | template { 176 | display: none; 177 | } 178 | 179 | [hidden] { 180 | display: none; 181 | } 182 | -------------------------------------------------------------------------------- /_gulp/vendors/reset.sass: -------------------------------------------------------------------------------- 1 | *, *::before, *::after 2 | box-sizing: border-box 3 | * 4 | margin: 0 5 | body 6 | line-height: 1.5 7 | -webkit-font-smoothing: antialiased 8 | img, picture, video, canvas, svg 9 | display: block 10 | max-width: 100% 11 | input, button, textarea, select 12 | font: inherit 13 | p, h1, h2, h3, h4, h5, h6 14 | overflow-wrap: break-word 15 | #root, #__next 16 | isolation: isolate 17 | -------------------------------------------------------------------------------- /_gulp/vendors/reset.scss: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | * { 6 | margin: 0; 7 | } 8 | 9 | body { 10 | line-height: 1.5; 11 | -webkit-font-smoothing: antialiased; 12 | } 13 | 14 | img, picture, video, canvas, svg { 15 | display: block; 16 | max-width: 100%; 17 | } 18 | 19 | input, button, textarea, select { 20 | font: inherit; 21 | } 22 | 23 | p, h1, h2, h3, h4, h5, h6 { 24 | overflow-wrap: break-word; 25 | } 26 | 27 | #root, #__next { 28 | isolation: isolate; 29 | } 30 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 2 | const { src, dest, watch, series, parallel } = require('gulp'); 3 | const sass = require('gulp-sass')(require('sass')); 4 | const autoprefixer = require('gulp-autoprefixer'); 5 | const cleanCSS = require('gulp-clean-css'); 6 | const browserify = require('browserify'); 7 | const babelify = require('babelify'); 8 | const source = require('vinyl-source-stream'); 9 | const buffer = require('vinyl-buffer'); 10 | const uglify = require('gulp-uglify'); 11 | const rename = require('gulp-rename'); 12 | const browserSync = require('browser-sync').create(); 13 | const imagemin = require('gulp-imagemin'); 14 | const del = require('del'); 15 | const plumber = require('gulp-plumber'); 16 | const sourcemaps = require('gulp-sourcemaps'); 17 | const gulpif = require('gulp-if'); 18 | const pug = require('gulp-pug'); 19 | const typescript = require('gulp-typescript'); 20 | const tsify = require('tsify'); 21 | 22 | const production = process.env.NODE_ENV === 'production'; 23 | 24 | async function clean() { 25 | await del(['dist']); 26 | } 27 | 28 | function styles() { 29 | return src('src/sass/**/*.sass') 30 | .pipe(plumber()) 31 | .pipe(gulpif(!production, sourcemaps.init())) 32 | .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) 33 | .pipe(autoprefixer()) 34 | .pipe(cleanCSS()) 35 | .pipe(gulpif(!production, sourcemaps.write('.'))) 36 | .pipe(dest('dist/css')) 37 | .pipe(browserSync.stream()); 38 | } 39 | 40 | function scripts() { 41 | const b = browserify({ 42 | entries: 'src/ts/main.ts', 43 | debug: !production, 44 | }) 45 | .transform(babelify, { 46 | presets: ['@babel/preset-env'], 47 | extensions: ['.js', '.ts'] 48 | }); 49 | 50 | b.plugin(tsify); 51 | 52 | return b.bundle() 53 | .pipe(source('main.js')) 54 | .pipe(buffer()) 55 | .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) 56 | .pipe(dest('dist/js')) 57 | .pipe(uglify()) 58 | .pipe(rename('main.min.js')) 59 | .pipe(gulpif(!production, sourcemaps.write('.'))) 60 | .pipe(dest('dist/js')); 61 | } 62 | 63 | function markup() { 64 | return src('src/pug/**/*.pug') 65 | .pipe(plumber()) 66 | .pipe(pug()) 67 | .pipe(dest('dist')); 68 | } 69 | 70 | function images() { 71 | return src('src/img/**/*') 72 | .pipe(imagemin()) 73 | .pipe(dest('dist/img')); 74 | } 75 | 76 | function serve(cb) { 77 | browserSync.init({ 78 | server: { 79 | baseDir: './dist' 80 | }, 81 | open: true 82 | }); 83 | cb(); 84 | } 85 | 86 | function watchFiles(cb) { 87 | watch('src/sass/**/*.sass', styles); 88 | watch('src/ts/**/*.ts', series(scripts, reload)); 89 | watch('src/pug/**/*.pug', series(markup, reload)); 90 | watch('src/img/**/*', series(images, reload)); 91 | cb(); 92 | } 93 | 94 | function reload(cb) { 95 | browserSync.reload(); 96 | cb(); 97 | } 98 | 99 | exports.clean = clean; 100 | exports.styles = styles; 101 | exports.scripts = scripts; 102 | exports.markup = markup; 103 | exports.images = images; 104 | exports.watch = watchFiles; 105 | 106 | exports.build = series(clean, parallel(styles, scripts, markup, images)); 107 | exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-boilerplate-starter-kit", 3 | "version": "4.0.0", 4 | "description": "A Front-end web kit and boilerplate for building web apps or small sites", 5 | "main": "gulpfile.js", 6 | "type": "commonjs", 7 | "scripts": { 8 | "postinstall": "ts-node _gulp/gulpSetup.ts", 9 | "start": "NODE_OPTIONS=--no-deprecation gulp", 10 | "build": "NODE_OPTIONS=--no-deprecation gulp build" 11 | }, 12 | "author": "Raniro Coelho", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@babel/core": "^7.25.2", 16 | "@babel/preset-env": "^7.25.3", 17 | "@types/browser-sync": "^2.27.0", 18 | "@types/browserify": "^12.0.40", 19 | "@types/gulp": "^4.0.10", 20 | "@types/gulp-autoprefixer": "^0.0.33", 21 | "@types/gulp-clean-css": "^4.3.4", 22 | "@types/gulp-if": "^0.0.34", 23 | "@types/gulp-imagemin": "^7.0.3", 24 | "@types/gulp-rename": "^2.0.1", 25 | "@types/gulp-sass": "^5.0.0", 26 | "@types/gulp-sourcemaps": "^0.0.35", 27 | "@types/gulp-uglify": "^3.0.7", 28 | "@types/inquirer": "^8.2.5", 29 | "@types/node": "^20.3.1", 30 | "@types/pug": "^2.0.10", 31 | "@types/vinyl-buffer": "^1.0.3", 32 | "@types/vinyl-source-stream": "^0.0.34", 33 | "babelify": "^10.0.0", 34 | "browser-sync": "^2.29.3", 35 | "browserify": "^17.0.0", 36 | "del": "^6.1.1", 37 | "gulp": "^4.0.2", 38 | "gulp-autoprefixer": "^8.0.0", 39 | "gulp-clean-css": "^4.3.0", 40 | "gulp-if": "^3.0.0", 41 | "gulp-imagemin": "^7.1.0", 42 | "gulp-plumber": "^1.2.1", 43 | "gulp-pug": "^5.0.0", 44 | "gulp-rename": "^2.0.0", 45 | "gulp-sass": "^5.1.0", 46 | "gulp-sourcemaps": "^3.0.0", 47 | "gulp-typescript": "^6.0.0-alpha.1", 48 | "gulp-uglify": "^3.0.2", 49 | "inquirer": "^8.2.5", 50 | "sass": "^1.63.6", 51 | "ts-node": "^10.9.1", 52 | "tsify": "^5.0.4", 53 | "typescript": "^5.1.3", 54 | "vinyl-buffer": "^1.0.1", 55 | "vinyl-source-stream": "^2.0.0" 56 | }, 57 | "dependencies": { 58 | "immutable": "^4.3.7" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018", "dom"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "outDir": "./dist", 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictBindCallApply": true, 16 | "strictPropertyInitialization": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "moduleResolution": "node", 24 | "baseUrl": "./", 25 | "paths": { 26 | "*": ["node_modules/*", "_gulp/types/*", "./typings"] 27 | }, 28 | "esModuleInterop": true, 29 | "experimentalDecorators": true, 30 | "emitDecoratorMetadata": true, 31 | "forceConsistentCasingInFileNames": true, 32 | "types": ["node", "gulp", "gulp-imagemin", "browserify", "gulp-rename", "gulp-uglify", "vinyl-buffer", "vinyl-source-stream"], 33 | "typeRoots": ["node_modules/@types", "_gulp/types", "./typings"] 34 | }, 35 | "include": [ 36 | "_gulp/**/*", 37 | "_gulp/types/custom.d.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "**/*.spec.ts" 42 | ] 43 | } -------------------------------------------------------------------------------- /typings/custon.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'gulp-imagemin'; 2 | declare module 'browserify'; 3 | declare module 'gulp-rename'; 4 | declare module 'gulp-uglify'; 5 | declare module 'vinyl-buffer'; 6 | declare module 'vinyl-source-stream'; --------------------------------------------------------------------------------