diff --git a/README.md b/README.md index 8f8f96b..d12f9d0 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ This repository now includes: - Unit tests for filesystem utilities (`npm test`) - CI workflow for typecheck + tests (GitHub Actions) - Contributing and security documentation +- A single runtime architecture: setup always generates one `gulpfile.js` from user choices (no parallel dynamic task runtime) --- diff --git a/_gulp/combineTasks.ts b/_gulp/combineTasks.ts deleted file mode 100644 index a8fb77b..0000000 --- a/_gulp/combineTasks.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { readFile, writeFile } from 'fs/promises'; -import { parallel, series, TaskFunction } from 'gulp'; -import { browserSyncServe, createWatchTask } from './modules/buildEnvironment'; -import { copyVendorCSS, createProjectFiles, createProjectStructure } from './modules/fileSetup'; -import { confirmProjectDeletion, promptUser } from './modules/setupQuestions'; -import { UserChoices } from './types'; -import { deleteDirectory, fileExists } from './utils/fileSystem'; -import { logger } from './utils/logger'; - -async function loadUserChoices(): Promise { - try { - const data = await readFile('_gulp/user-choices.json', 'utf8'); - return JSON.parse(data); - } catch (error) { - logger.error('Failed to load user choices. Please run setup again.'); - process.exit(1); - } -} - -async function createGulpTasks() { - const choices = await loadUserChoices(); - - try { - const { styleTask } = await import('./tasks/styleTask'); - const { scriptTask } = await import('./tasks/scriptTask'); - const { markupTask } = await import('./tasks/markupTask'); - const { imagesTask } = await import('./tasks/imagesTask'); - const { cleanTask } = await import('./tasks/cleanTask'); - - const watchTask = createWatchTask(choices, { - styleTask: styleTask(choices) as TaskFunction, - scriptTask: scriptTask(choices) as TaskFunction, - markupTask: markupTask(choices) as TaskFunction, - imagesTask: imagesTask() as TaskFunction - }); - - const defaultTask: TaskFunction = (done) => { - return series( - cleanTask(), - parallel( - styleTask(choices), - scriptTask(choices), - markupTask(choices), - imagesTask() - ), - browserSyncServe, - watchTask - )(done); - }; - - const buildTask: TaskFunction = (done) => { - return series( - cleanTask(), - parallel( - styleTask(choices), - scriptTask(choices), - markupTask(choices), - imagesTask() - ) - )(done); - }; - - return { defaultTask, buildTask }; - } catch (error) { - logger.error(`Failed to create Gulp tasks: ${(error as Error).message}`); - process.exit(1); - } -} - -export async function setup(): Promise { - try { - const projectExists = fileExists('src') || fileExists('dist'); - if (projectExists) { - const shouldDelete = await confirmProjectDeletion(); - if (!shouldDelete) { - logger.info('Project setup canceled. Exiting...'); - return; - } - deleteDirectory('src'); - deleteDirectory('dist'); - } - - const choices: UserChoices = await promptUser(); - - await writeFile('_gulp/user-choices.json', JSON.stringify(choices, null, 2)); - - createProjectStructure(choices); - createProjectFiles(choices); - copyVendorCSS(choices); - - logger.success('Setup complete. Gulpfile has been generated.'); - logger.info('Starting development server...'); - - const { defaultTask } = await createGulpTasks(); - defaultTask((err?: Error | null) => { - if (err) { - logger.error(`Development server failed: ${err.message}`); - } else { - logger.success('Development server started'); - } - }); - } catch (error: unknown) { - logger.error(`An error occurred during setup: ${(error as Error).message}`); - } -} - -setup().catch(error => { - logger.error(`An error occurred: ${(error as Error).message}`); - process.exit(1); -}); \ No newline at end of file diff --git a/_gulp/gulpSetup.ts b/_gulp/gulpSetup.ts index 70b45a1..d378cc3 100644 --- a/_gulp/gulpSetup.ts +++ b/_gulp/gulpSetup.ts @@ -3,6 +3,7 @@ import { writeFile } from 'fs/promises'; import { copyVendorCSS, createProjectFiles, createProjectStructure } from './modules/fileSetup'; import { generateGulpfile } from './modules/gulpfileGenerator'; import { confirmProjectDeletion, promptUser } from './modules/setupQuestions'; +import { assertUserChoices } from './modules/userChoicesValidation'; import { UserChoices } from './types'; import { deleteDirectory, fileExists } from './utils/fileSystem'; import { logger } from './utils/logger'; @@ -20,7 +21,8 @@ async function setup(): Promise { deleteDirectory('dist'); } - const choices: UserChoices = await promptUser(); + const rawChoices = await promptUser(); + const choices: UserChoices = assertUserChoices(rawChoices); await writeFile('_gulp/user-choices.json', JSON.stringify(choices, null, 2)); @@ -32,7 +34,7 @@ async function setup(): Promise { logger.success('Setup complete. Gulpfile has been generated.'); logger.info('Starting development server...'); - exec('yarn start', (error, stdout, stderr) => { + exec('npm start', (error, stdout, stderr) => { if (error) { logger.error(`Error: ${error.message}`); return; diff --git a/_gulp/modules/buildEnvironment.ts b/_gulp/modules/buildEnvironment.ts deleted file mode 100644 index bba6b05..0000000 --- a/_gulp/modules/buildEnvironment.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { create as createBrowserSync } from 'browser-sync'; -import { series, TaskFunction, watch } from 'gulp'; -import { UserChoices } from '../types'; - -const bs = createBrowserSync(); - -export function browserSyncServe(cb: () => void): void { - bs.init({ - server: { baseDir: 'dist/' }, - open: false, - notify: false - }); - cb(); -} - -export function browserSyncReload(cb: () => void): void { - bs.reload(); - cb(); -} - -interface Tasks { - styleTask: TaskFunction; - scriptTask: TaskFunction; - markupTask: TaskFunction; - imagesTask: TaskFunction; -} - -export function createWatchTask(choices: UserChoices, tasks: Tasks) { - return function watchTask(): void { - const stylePath = `src/${choices.style === 'Sass' ? 'sass' : 'scss'}/**/*.${choices.style === 'Sass' ? 'sass' : 'scss'}`; - const scriptPath = `src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}`; - const markupPath = `src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}`; - const imgPath = 'src/img/**/*'; - - watch(stylePath, series(tasks.styleTask, browserSyncReload)); - watch(scriptPath, series(tasks.scriptTask, browserSyncReload)); - watch(markupPath, series(tasks.markupTask, browserSyncReload)); - watch(imgPath, series(tasks.imagesTask, browserSyncReload)); - }; -} \ No newline at end of file diff --git a/_gulp/modules/fileSetup.ts b/_gulp/modules/fileSetup.ts index 18fbc10..2b6e2c2 100644 --- a/_gulp/modules/fileSetup.ts +++ b/_gulp/modules/fileSetup.ts @@ -1,12 +1,15 @@ +import { resolveProjectPaths } from './pathConfig'; import { UserChoices } from '../types'; import { copyFile, createDirectory, writeFile } from '../utils/fileSystem'; export function createProjectStructure(choices: UserChoices): void { + const paths = resolveProjectPaths(choices); + const dirs = [ 'src', - `src/${choices.script === 'JavaScript' ? 'js' : 'ts'}`, - `src/${choices.style === 'Sass' ? 'sass/base' : 'scss/base'}`, // Updated path for base - `src/${choices.markup === 'HTML' ? 'html' : 'pug'}`, + `src/${paths.scriptFolder}`, + `src/${paths.styleBaseFolder}`, + `src/${paths.markupFolder}`, 'src/img', 'dist/css' ]; @@ -15,35 +18,34 @@ export function createProjectStructure(choices: UserChoices): void { } export function createProjectFiles(choices: UserChoices): void { - const scriptExt = choices.script === 'JavaScript' ? 'js' : 'ts'; - const styleExt = choices.style === 'Sass' ? 'sass' : 'scss'; + const paths = resolveProjectPaths(choices); const scriptContent = choices.script === 'JavaScript' ? 'console.log("Hello, World!");' : 'console.log("Hello, TypeScript!");'; - writeFile(`src/${scriptExt}/main.${scriptExt}`, scriptContent); + writeFile(`src/${paths.scriptFolder}/main.${paths.scriptExtension}`, scriptContent); let styleContent = `// Main ${choices.style} file\n`; if (choices.addNormalize) styleContent += choices.style === 'Sass' ? "@import 'base/normalize'\n" : "@import 'base/normalize';\n"; if (choices.addReset) styleContent += choices.style === 'Sass' ? "@import 'base/reset'\n" : "@import 'base/reset';\n"; - writeFile(`src/${styleExt}/main.${styleExt}`, styleContent); + writeFile(`src/${paths.styleFolder}/main.${paths.styleExtension}`, styleContent); const markupContent = choices.markup === 'HTML' ? getHtmlTemplate() : getPugTemplate(); - const markupFilePath = choices.markup === 'HTML' ? 'src/html/index.html' : 'src/pug/index.pug'; - - writeFile(markupFilePath, markupContent); + writeFile(`src/${paths.markupFolder}/index.${paths.markupExtension}`, markupContent); writeFile('src/favicon.ico', ''); } export function copyVendorCSS(choices: UserChoices): void { + const paths = resolveProjectPaths(choices); + const vendorFiles = [ - { type: 'Normalize', src: `_gulp/vendors/normalize.${choices.style === 'Sass' ? 'sass' : 'scss'}` }, - { type: 'Reset', src: `_gulp/vendors/reset.${choices.style === 'Sass' ? 'sass' : 'scss'}` } + { type: 'Normalize', src: `_gulp/vendors/normalize.${paths.styleExtension}` }, + { type: 'Reset', src: `_gulp/vendors/reset.${paths.styleExtension}` } ]; vendorFiles.forEach(file => { if (choices[`add${file.type}` as keyof UserChoices]) { - const dest = `src/${choices.style === 'Sass' ? 'sass/base' : 'scss/base'}/_${file.type.toLowerCase()}.${choices.style === 'Sass' ? 'sass' : 'scss'}`; // Save in base folder + const dest = `src/${paths.styleBaseFolder}/_${file.type.toLowerCase()}.${paths.styleExtension}`; copyFile(file.src, dest); } }); diff --git a/_gulp/modules/gulpfileGenerator.ts b/_gulp/modules/gulpfileGenerator.ts index 5b8f8d2..00e869a 100644 --- a/_gulp/modules/gulpfileGenerator.ts +++ b/_gulp/modules/gulpfileGenerator.ts @@ -1,115 +1,8 @@ import { UserChoices } from '../types'; import { writeFile } from '../utils/fileSystem'; +import { buildGulpfileTemplate } from '../templates/gulpfile'; export function generateGulpfile(choices: UserChoices): void { - const gulpfileContent = ` -const { src, dest, watch, series, parallel } = require('gulp'); -const sass = require('gulp-sass')(require('sass')); -const autoprefixer = require('gulp-autoprefixer'); -const cleanCSS = require('gulp-clean-css'); -const browserify = require('browserify'); -const babelify = require('babelify'); -const source = require('vinyl-source-stream'); -const buffer = require('vinyl-buffer'); -const uglify = require('gulp-uglify'); -const rename = require('gulp-rename'); -const browserSync = require('browser-sync').create(); -const imagemin = require('gulp-imagemin'); -const del = require('del'); -const plumber = require('gulp-plumber'); -const sourcemaps = require('gulp-sourcemaps'); -const gulpif = require('gulp-if'); -const pug = ${choices.markup === 'Pug' ? "require('gulp-pug')" : 'null'}; -const typescript = ${choices.script === 'TypeScript' ? "require('gulp-typescript')" : 'null'}; -const tsify = ${choices.script === 'TypeScript' ? "require('tsify')" : 'null'}; - -const production = process.env.NODE_ENV === 'production'; - -async function clean() { - await del(['dist']); -} - -function styles() { - return src('src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}') - .pipe(plumber()) - .pipe(gulpif(!production, sourcemaps.init())) - .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) - .pipe(autoprefixer()) - .pipe(cleanCSS()) - .pipe(gulpif(!production, sourcemaps.write('.'))) - .pipe(dest('dist/css')) - .pipe(browserSync.stream()); + const gulpfileContent = buildGulpfileTemplate(choices); + writeFile('gulpfile.js', `${gulpfileContent}\n`); } - -function scripts() { - const b = browserify({ - entries: 'src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/main.${choices.script === 'TypeScript' ? 'ts' : 'js'}', - debug: !production, - }) - .transform(babelify, { - presets: ['@babel/preset-env'], - extensions: ['.js', '.ts'] - }); - - ${choices.script === 'TypeScript' ? 'b.plugin(tsify);' : ''} - - return b.bundle() - .pipe(source('main.js')) - .pipe(buffer()) - .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) - .pipe(dest('dist/js')) - .pipe(uglify()) - .pipe(rename('main.min.js')) - .pipe(gulpif(!production, sourcemaps.write('.'))) - .pipe(dest('dist/js')); -} - -function markup() { - return src('src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}') - .pipe(plumber()) - ${choices.markup === 'Pug' ? '.pipe(pug())' : ''} - .pipe(dest('dist')); -} - -function images() { - return src('src/img/**/*') - .pipe(imagemin()) - .pipe(dest('dist/img')); -} - -function serve(cb) { - browserSync.init({ - server: { - baseDir: './dist' - }, - open: true - }); - cb(); -} - -function watchFiles(cb) { - watch('src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}', styles); - watch('src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}', series(scripts, reload)); - watch('src/${choices.markup === 'Pug' ? 'pug' : 'html'}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}', series(markup, reload)); - watch('src/img/**/*', series(images, reload)); - cb(); -} - -function reload(cb) { - browserSync.reload(); - cb(); -} - -exports.clean = clean; -exports.styles = styles; -exports.scripts = scripts; -exports.markup = markup; -exports.images = images; -exports.watch = watchFiles; - -exports.build = series(clean, parallel(styles, scripts, markup, images)); -exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); -`; - - writeFile('gulpfile.js', gulpfileContent); -} \ No newline at end of file diff --git a/_gulp/modules/gulpfileSections.test.ts b/_gulp/modules/gulpfileSections.test.ts new file mode 100644 index 0000000..257374a --- /dev/null +++ b/_gulp/modules/gulpfileSections.test.ts @@ -0,0 +1,33 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { markupSection } from '../templates/gulpfile/sections'; +import { createGulpTemplateContext } from '../templates/gulpfile/types'; + +test('markupSection includes pug compilation when markup is Pug', () => { + const context = createGulpTemplateContext({ + script: 'JavaScript', + style: 'Sass', + markup: 'Pug', + addNormalize: false, + addReset: false, + }); + + const output = markupSection(context); + + assert.match(output, /\.pipe\(pug\(\)\)/); +}); + +test('markupSection does not inject pug compilation when markup is HTML', () => { + const context = createGulpTemplateContext({ + script: 'TypeScript', + style: 'SCSS', + markup: 'HTML', + addNormalize: true, + addReset: true, + }); + + const output = markupSection(context); + + assert.doesNotMatch(output, /\.pipe\(pug\(\)\)/); + assert.match(output, /\.pipe\(plumber\(\)\)\n\s*\.pipe\(dest\('dist'\)\)/); +}); diff --git a/_gulp/modules/gulpfileTemplateSnapshots.test.ts b/_gulp/modules/gulpfileTemplateSnapshots.test.ts new file mode 100644 index 0000000..f77c77f --- /dev/null +++ b/_gulp/modules/gulpfileTemplateSnapshots.test.ts @@ -0,0 +1,65 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { buildGulpfileTemplate } from '../templates/gulpfile'; +import { UserChoices } from '../types'; + +interface MatrixCase { + name: string; + choices: UserChoices; + snapshotFile: string; +} + +const cases: MatrixCase[] = [ + { + name: 'JavaScript + Sass + HTML', + choices: { script: 'JavaScript', style: 'Sass', markup: 'HTML', addNormalize: false, addReset: false }, + snapshotFile: 'js-sass-html.gulpfile.snap', + }, + { + name: 'JavaScript + Sass + Pug', + choices: { script: 'JavaScript', style: 'Sass', markup: 'Pug', addNormalize: false, addReset: false }, + snapshotFile: 'js-sass-pug.gulpfile.snap', + }, + { + name: 'JavaScript + SCSS + HTML', + choices: { script: 'JavaScript', style: 'SCSS', markup: 'HTML', addNormalize: false, addReset: false }, + snapshotFile: 'js-scss-html.gulpfile.snap', + }, + { + name: 'JavaScript + SCSS + Pug', + choices: { script: 'JavaScript', style: 'SCSS', markup: 'Pug', addNormalize: false, addReset: false }, + snapshotFile: 'js-scss-pug.gulpfile.snap', + }, + { + name: 'TypeScript + Sass + HTML', + choices: { script: 'TypeScript', style: 'Sass', markup: 'HTML', addNormalize: false, addReset: false }, + snapshotFile: 'ts-sass-html.gulpfile.snap', + }, + { + name: 'TypeScript + Sass + Pug', + choices: { script: 'TypeScript', style: 'Sass', markup: 'Pug', addNormalize: false, addReset: false }, + snapshotFile: 'ts-sass-pug.gulpfile.snap', + }, + { + name: 'TypeScript + SCSS + HTML', + choices: { script: 'TypeScript', style: 'SCSS', markup: 'HTML', addNormalize: false, addReset: false }, + snapshotFile: 'ts-scss-html.gulpfile.snap', + }, + { + name: 'TypeScript + SCSS + Pug', + choices: { script: 'TypeScript', style: 'SCSS', markup: 'Pug', addNormalize: false, addReset: false }, + snapshotFile: 'ts-scss-pug.gulpfile.snap', + }, +]; + +for (const scenario of cases) { + test(`buildGulpfileTemplate snapshot: ${scenario.name}`, () => { + const actual = `${buildGulpfileTemplate(scenario.choices)}\n`; + const snapshotPath = path.join('_gulp', 'templates', 'gulpfile', '__snapshots__', scenario.snapshotFile); + const expected = fs.readFileSync(snapshotPath, 'utf8'); + + assert.equal(actual, expected); + }); +} diff --git a/_gulp/modules/pathConfig.test.ts b/_gulp/modules/pathConfig.test.ts new file mode 100644 index 0000000..7717307 --- /dev/null +++ b/_gulp/modules/pathConfig.test.ts @@ -0,0 +1,37 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { resolveProjectPaths } from './pathConfig'; + +test('resolveProjectPaths returns correct folders/extensions for Sass + JavaScript + HTML', () => { + const result = resolveProjectPaths({ + script: 'JavaScript', + style: 'Sass', + markup: 'HTML', + addNormalize: false, + addReset: false, + }); + + assert.equal(result.scriptFolder, 'js'); + assert.equal(result.scriptExtension, 'js'); + assert.equal(result.styleFolder, 'sass'); + assert.equal(result.styleBaseFolder, 'sass/base'); + assert.equal(result.markupFolder, 'html'); + assert.equal(result.markupExtension, 'html'); +}); + +test('resolveProjectPaths returns correct folders/extensions for SCSS + TypeScript + Pug', () => { + const result = resolveProjectPaths({ + script: 'TypeScript', + style: 'SCSS', + markup: 'Pug', + addNormalize: true, + addReset: true, + }); + + assert.equal(result.scriptFolder, 'ts'); + assert.equal(result.scriptExtension, 'ts'); + assert.equal(result.styleFolder, 'scss'); + assert.equal(result.styleBaseFolder, 'scss/base'); + assert.equal(result.markupFolder, 'pug'); + assert.equal(result.markupExtension, 'pug'); +}); diff --git a/_gulp/modules/pathConfig.ts b/_gulp/modules/pathConfig.ts new file mode 100644 index 0000000..407e2e7 --- /dev/null +++ b/_gulp/modules/pathConfig.ts @@ -0,0 +1,25 @@ +import { UserChoices } from '../types'; + +export interface ResolvedProjectPaths { + scriptFolder: 'js' | 'ts'; + scriptExtension: 'js' | 'ts'; + styleFolder: 'sass' | 'scss'; + styleExtension: 'sass' | 'scss'; + styleBaseFolder: 'sass/base' | 'scss/base'; + markupFolder: 'html' | 'pug'; + markupExtension: 'html' | 'pug'; +} + +export function resolveProjectPaths(choices: UserChoices): ResolvedProjectPaths { + const styleFolder = choices.style === 'Sass' ? 'sass' : 'scss'; + + return { + scriptFolder: choices.script === 'TypeScript' ? 'ts' : 'js', + scriptExtension: choices.script === 'TypeScript' ? 'ts' : 'js', + styleFolder, + styleExtension: styleFolder, + styleBaseFolder: choices.style === 'Sass' ? 'sass/base' : 'scss/base', + markupFolder: choices.markup === 'Pug' ? 'pug' : 'html', + markupExtension: choices.markup === 'Pug' ? 'pug' : 'html', + }; +} diff --git a/_gulp/modules/userChoicesValidation.test.ts b/_gulp/modules/userChoicesValidation.test.ts new file mode 100644 index 0000000..c7616d2 --- /dev/null +++ b/_gulp/modules/userChoicesValidation.test.ts @@ -0,0 +1,31 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { assertUserChoices, isUserChoices } from './userChoicesValidation'; + +test('isUserChoices returns true for a valid prompt payload', () => { + const valid = { + script: 'TypeScript', + style: 'SCSS', + markup: 'Pug', + addNormalize: true, + addReset: false, + }; + + assert.equal(isUserChoices(valid), true); +}); + +test('isUserChoices returns false for invalid payload shape', () => { + const invalid = { + script: 'CoffeeScript', + style: 'SCSS', + markup: 'Pug', + addNormalize: true, + addReset: false, + }; + + assert.equal(isUserChoices(invalid), false); +}); + +test('assertUserChoices throws on invalid choices', () => { + assert.throws(() => assertUserChoices({ script: 'JavaScript' }), /Invalid user choices/); +}); diff --git a/_gulp/modules/userChoicesValidation.ts b/_gulp/modules/userChoicesValidation.ts new file mode 100644 index 0000000..a4b92cb --- /dev/null +++ b/_gulp/modules/userChoicesValidation.ts @@ -0,0 +1,29 @@ +import { UserChoices } from '../types'; + +const validScripts = new Set(['JavaScript', 'TypeScript']); +const validStyles = new Set(['Sass', 'SCSS']); +const validMarkups = new Set(['HTML', 'Pug']); + +function isObjectRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function isUserChoices(value: unknown): value is UserChoices { + if (!isObjectRecord(value)) { + return false; + } + + return validScripts.has(value.script as UserChoices['script']) + && validStyles.has(value.style as UserChoices['style']) + && validMarkups.has(value.markup as UserChoices['markup']) + && typeof value.addNormalize === 'boolean' + && typeof value.addReset === 'boolean'; +} + +export function assertUserChoices(value: unknown): UserChoices { + if (!isUserChoices(value)) { + throw new Error('Invalid user choices received from prompts. Please re-run setup.'); + } + + return value; +} diff --git a/_gulp/tasks/cleanTask.ts b/_gulp/tasks/cleanTask.ts deleted file mode 100644 index 3a43dfb..0000000 --- a/_gulp/tasks/cleanTask.ts +++ /dev/null @@ -1,7 +0,0 @@ -import del from 'del'; - -export function cleanTask() { - return function(cb: (error?: Error | null) => void) { - del(['dist']).then(() => cb()).catch(cb); - }; -} \ No newline at end of file diff --git a/_gulp/tasks/imagesTask.ts b/_gulp/tasks/imagesTask.ts deleted file mode 100644 index 31e27dc..0000000 --- a/_gulp/tasks/imagesTask.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { dest, src, TaskFunction } from 'gulp'; -import imagemin from 'gulp-imagemin'; - -export function imagesTask(): TaskFunction { - return function() { - return src('src/img/**/*') - .pipe(imagemin()) - .pipe(dest('dist/img')); - }; -} \ No newline at end of file diff --git a/_gulp/tasks/markupTask.ts b/_gulp/tasks/markupTask.ts deleted file mode 100644 index 625af01..0000000 --- a/_gulp/tasks/markupTask.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { dest, src } from 'gulp'; -import plumber from 'gulp-plumber'; -import pug from 'gulp-pug'; -import { UserChoices } from '../types'; - -export function markupTask(choices: UserChoices) { - return function() { - return src(`src/${choices.markup.toLowerCase()}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}`) - .pipe(plumber()) - .pipe(choices.markup === 'Pug' ? pug() : plumber()) - .pipe(dest('dist')); - }; -} \ No newline at end of file diff --git a/_gulp/tasks/scriptTask.ts b/_gulp/tasks/scriptTask.ts deleted file mode 100644 index 117c21e..0000000 --- a/_gulp/tasks/scriptTask.ts +++ /dev/null @@ -1,32 +0,0 @@ -import browserify from 'browserify'; -import { dest, TaskFunction } from 'gulp'; -import rename from 'gulp-rename'; -import sourcemaps from 'gulp-sourcemaps'; -import uglify from 'gulp-uglify'; -import tsify from 'tsify'; -import buffer from 'vinyl-buffer'; -import source from 'vinyl-source-stream'; -import { UserChoices } from '../types'; - -export function scriptTask(choices: UserChoices): TaskFunction { - return function() { - const b = browserify({ - entries: `src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/main.${choices.script === 'TypeScript' ? 'ts' : 'js'}`, - debug: true, - }); - - if (choices.script === 'TypeScript') { - b.plugin(tsify); - } - - return b.bundle() - .pipe(source('main.js')) - .pipe(buffer()) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(dest('dist/js')) - .pipe(uglify()) - .pipe(rename('main.min.js')) - .pipe(sourcemaps.write('./')) - .pipe(dest('dist/js')); - }; -} \ No newline at end of file diff --git a/_gulp/tasks/styleTask.ts b/_gulp/tasks/styleTask.ts deleted file mode 100644 index 42b14b0..0000000 --- a/_gulp/tasks/styleTask.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { dest, src, TaskFunction } from 'gulp'; -import autoprefixer from 'gulp-autoprefixer'; -import cleanCSS from 'gulp-clean-css'; -import plumber from 'gulp-plumber'; -import gulpSass from 'gulp-sass'; -import sourcemaps from 'gulp-sourcemaps'; -import * as sass from 'sass'; -import { UserChoices } from '../types'; - -const sassCompiler = gulpSass(sass); - -export function styleTask(choices: UserChoices): TaskFunction { - return function() { - return src(`src/${choices.style.toLowerCase()}/**/*.${choices.style.toLowerCase()}`, { sourcemaps: true }) - .pipe(plumber()) - .pipe(sassCompiler({ indentedSyntax: choices.style === 'Sass' })) - .pipe(autoprefixer()) - .pipe(cleanCSS()) - .pipe(sourcemaps.write('.')) - .pipe(dest('dist/css')); - }; -} \ No newline at end of file diff --git a/_gulp/templates/gulpfile/__snapshots__/js-sass-html.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/js-sass-html.gulpfile.snap new file mode 100644 index 0000000..9e52313 --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/js-sass-html.gulpfile.snap @@ -0,0 +1,104 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = null; +const tsify = null; + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/sass/**/*.sass') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/js/main.js', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/html/**/*.html') + .pipe(plumber()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/sass/**/*.sass', styles); + watch('src/js/**/*.js', series(scripts, reload)); + watch('src/html/**/*.html', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/js-sass-pug.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/js-sass-pug.gulpfile.snap new file mode 100644 index 0000000..7b4bd8c --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/js-sass-pug.gulpfile.snap @@ -0,0 +1,105 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = require('gulp-pug'); +const tsify = null; + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/sass/**/*.sass') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/js/main.js', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/pug/**/*.pug') + .pipe(plumber()) + .pipe(pug()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/sass/**/*.sass', styles); + watch('src/js/**/*.js', series(scripts, reload)); + watch('src/pug/**/*.pug', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/js-scss-html.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/js-scss-html.gulpfile.snap new file mode 100644 index 0000000..4e27b4b --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/js-scss-html.gulpfile.snap @@ -0,0 +1,104 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = null; +const tsify = null; + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/scss/**/*.scss') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/js/main.js', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/html/**/*.html') + .pipe(plumber()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/scss/**/*.scss', styles); + watch('src/js/**/*.js', series(scripts, reload)); + watch('src/html/**/*.html', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/js-scss-pug.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/js-scss-pug.gulpfile.snap new file mode 100644 index 0000000..76627a6 --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/js-scss-pug.gulpfile.snap @@ -0,0 +1,105 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = require('gulp-pug'); +const tsify = null; + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/scss/**/*.scss') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/js/main.js', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/pug/**/*.pug') + .pipe(plumber()) + .pipe(pug()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/scss/**/*.scss', styles); + watch('src/js/**/*.js', series(scripts, reload)); + watch('src/pug/**/*.pug', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/ts-sass-html.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/ts-sass-html.gulpfile.snap new file mode 100644 index 0000000..523e0f8 --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/ts-sass-html.gulpfile.snap @@ -0,0 +1,104 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = null; +const tsify = require('tsify'); + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/sass/**/*.sass') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/ts/main.ts', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + b.plugin(tsify); + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/html/**/*.html') + .pipe(plumber()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/sass/**/*.sass', styles); + watch('src/ts/**/*.ts', series(scripts, reload)); + watch('src/html/**/*.html', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/ts-sass-pug.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/ts-sass-pug.gulpfile.snap new file mode 100644 index 0000000..9a604bb --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/ts-sass-pug.gulpfile.snap @@ -0,0 +1,105 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = require('gulp-pug'); +const tsify = require('tsify'); + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/sass/**/*.sass') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/ts/main.ts', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + b.plugin(tsify); + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/pug/**/*.pug') + .pipe(plumber()) + .pipe(pug()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/sass/**/*.sass', styles); + watch('src/ts/**/*.ts', series(scripts, reload)); + watch('src/pug/**/*.pug', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/ts-scss-html.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/ts-scss-html.gulpfile.snap new file mode 100644 index 0000000..3afd681 --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/ts-scss-html.gulpfile.snap @@ -0,0 +1,104 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = null; +const tsify = require('tsify'); + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/scss/**/*.scss') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/ts/main.ts', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + b.plugin(tsify); + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/html/**/*.html') + .pipe(plumber()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/scss/**/*.scss', styles); + watch('src/ts/**/*.ts', series(scripts, reload)); + watch('src/html/**/*.html', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/__snapshots__/ts-scss-pug.gulpfile.snap b/_gulp/templates/gulpfile/__snapshots__/ts-scss-pug.gulpfile.snap new file mode 100644 index 0000000..a746896 --- /dev/null +++ b/_gulp/templates/gulpfile/__snapshots__/ts-scss-pug.gulpfile.snap @@ -0,0 +1,105 @@ +const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = require('gulp-pug'); +const tsify = require('tsify'); + +const production = process.env.NODE_ENV === 'production'; + +async function clean() { + await del(['dist']); +} + +function styles() { + return src('src/scss/**/*.scss') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +} + +function scripts() { + const b = browserify({ + entries: 'src/ts/main.ts', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + b.plugin(tsify); + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +} + +function markup() { + return src('src/pug/**/*.pug') + .pipe(plumber()) + .pipe(pug()) + .pipe(dest('dist')); +} + +function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +} + +function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/scss/**/*.scss', styles); + watch('src/ts/**/*.ts', series(scripts, reload)); + watch('src/pug/**/*.pug', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +} + +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles); diff --git a/_gulp/templates/gulpfile/index.ts b/_gulp/templates/gulpfile/index.ts new file mode 100644 index 0000000..e43e24c --- /dev/null +++ b/_gulp/templates/gulpfile/index.ts @@ -0,0 +1,27 @@ +import { + cleanSection, + devServerSection, + exportsSection, + imagesSection, + importsSection, + markupSection, + scriptsSection, + stylesSection, +} from './sections'; +import { createGulpTemplateContext } from './types'; +import { UserChoices } from '../../types'; + +export function buildGulpfileTemplate(choices: UserChoices): string { + const context = createGulpTemplateContext(choices); + + return [ + importsSection(context), + cleanSection(), + stylesSection(context), + scriptsSection(context), + markupSection(context), + imagesSection(), + devServerSection(context), + exportsSection(), + ].join('\n\n'); +} diff --git a/_gulp/templates/gulpfile/sections.ts b/_gulp/templates/gulpfile/sections.ts new file mode 100644 index 0000000..a36e598 --- /dev/null +++ b/_gulp/templates/gulpfile/sections.ts @@ -0,0 +1,124 @@ +import { GulpTemplateContext } from './types'; + +export function importsSection({ choices }: GulpTemplateContext): string { + return `const { src, dest, watch, series, parallel } = require('gulp'); +const sass = require('gulp-sass')(require('sass')); +const autoprefixer = require('gulp-autoprefixer'); +const cleanCSS = require('gulp-clean-css'); +const browserify = require('browserify'); +const babelify = require('babelify'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); +const browserSync = require('browser-sync').create(); +const imagemin = require('gulp-imagemin'); +const del = require('del'); +const plumber = require('gulp-plumber'); +const sourcemaps = require('gulp-sourcemaps'); +const gulpif = require('gulp-if'); +const pug = ${choices.markup === 'Pug' ? "require('gulp-pug')" : 'null'}; +const tsify = ${choices.script === 'TypeScript' ? "require('tsify')" : 'null'}; + +const production = process.env.NODE_ENV === 'production';`; +} + +export function cleanSection(): string { + return `async function clean() { + await del(['dist']); +}`; +} + +export function stylesSection({ styleFolder, styleExtension }: GulpTemplateContext): string { + return `function styles() { + return src('src/${styleFolder}/**/*.${styleExtension}') + .pipe(plumber()) + .pipe(gulpif(!production, sourcemaps.init())) + .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(cleanCSS()) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/css')) + .pipe(browserSync.stream()); +}`; +} + +export function scriptsSection({ choices, scriptFolder, scriptExtension }: GulpTemplateContext): string { + return `function scripts() { + const b = browserify({ + entries: 'src/${scriptFolder}/main.${scriptExtension}', + debug: !production, + }) + .transform(babelify, { + presets: ['@babel/preset-env'], + extensions: ['.js', '.ts'] + }); + + ${choices.script === 'TypeScript' ? 'b.plugin(tsify);' : ''} + + return b.bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulpif(!production, sourcemaps.init({ loadMaps: true }))) + .pipe(dest('dist/js')) + .pipe(uglify()) + .pipe(rename('main.min.js')) + .pipe(gulpif(!production, sourcemaps.write('.'))) + .pipe(dest('dist/js')); +}`; +} + +export function markupSection({ choices, markupFolder, markupExtension }: GulpTemplateContext): string { + const compileMarkupPipe = choices.markup === 'Pug' ? '\n .pipe(pug())' : ''; + + return `function markup() { + return src('src/${markupFolder}/**/*.${markupExtension}') + .pipe(plumber())${compileMarkupPipe} + .pipe(dest('dist')); +}`; +} + +export function imagesSection(): string { + return `function images() { + return src('src/img/**/*') + .pipe(imagemin()) + .pipe(dest('dist/img')); +}`; +} + +export function devServerSection({ styleFolder, styleExtension, scriptFolder, scriptExtension, markupFolder, markupExtension }: GulpTemplateContext): string { + return `function serve(cb) { + browserSync.init({ + server: { + baseDir: './dist' + }, + open: true + }); + cb(); +} + +function watchFiles(cb) { + watch('src/${styleFolder}/**/*.${styleExtension}', styles); + watch('src/${scriptFolder}/**/*.${scriptExtension}', series(scripts, reload)); + watch('src/${markupFolder}/**/*.${markupExtension}', series(markup, reload)); + watch('src/img/**/*', series(images, reload)); + cb(); +} + +function reload(cb) { + browserSync.reload(); + cb(); +}`; +} + +export function exportsSection(): string { + return `exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.markup = markup; +exports.images = images; +exports.watch = watchFiles; + +exports.build = series(clean, parallel(styles, scripts, markup, images)); +exports.default = series(clean, parallel(styles, scripts, markup, images), serve, watchFiles);`; +} diff --git a/_gulp/templates/gulpfile/types.ts b/_gulp/templates/gulpfile/types.ts new file mode 100644 index 0000000..909d61f --- /dev/null +++ b/_gulp/templates/gulpfile/types.ts @@ -0,0 +1,13 @@ +import { resolveProjectPaths, ResolvedProjectPaths } from '../../modules/pathConfig'; +import { UserChoices } from '../../types'; + +export interface GulpTemplateContext extends ResolvedProjectPaths { + choices: UserChoices; +} + +export function createGulpTemplateContext(choices: UserChoices): GulpTemplateContext { + return { + choices, + ...resolveProjectPaths(choices), + }; +}