From b4567e26c6f97ba80ccc2952dc07c503a691a8db Mon Sep 17 00:00:00 2001 From: Raniro Coelho Date: Tue, 20 Aug 2024 10:56:27 -0300 Subject: [PATCH] Add browserify and dependencies to script bundle --- _gulp/combineTasks.ts | 115 +++++++++++++++++------------ _gulp/modules/buildEnvironment.ts | 19 +++-- _gulp/modules/gulpfileGenerator.ts | 43 +++++++---- _gulp/tasks/cleanTask.ts | 4 +- _gulp/tasks/imagesTask.ts | 12 +-- _gulp/tasks/markupTask.ts | 2 +- _gulp/tasks/scriptTask.ts | 38 +++++++--- _gulp/tasks/styleTask.ts | 10 +-- _gulp/user-choices.json | 2 +- gulpfile.js | 107 +++++++++++++++++++++++++++ package.json | 36 ++++++--- tsconfig.json | 9 ++- typings/custon.d.ts | 6 ++ typings/gulp-cssnano.d.ts | 1 - 14 files changed, 296 insertions(+), 108 deletions(-) create mode 100644 gulpfile.js create mode 100644 typings/custon.d.ts delete mode 100644 typings/gulp-cssnano.d.ts diff --git a/_gulp/combineTasks.ts b/_gulp/combineTasks.ts index 800a5f1..a8fb77b 100644 --- a/_gulp/combineTasks.ts +++ b/_gulp/combineTasks.ts @@ -1,11 +1,14 @@ -import { parallel, series } from 'gulp'; +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 { readFile } = await import('fs/promises'); const data = await readFile('_gulp/user-choices.json', 'utf8'); return JSON.parse(data); } catch (error) { @@ -25,33 +28,37 @@ async function createGulpTasks() { const { cleanTask } = await import('./tasks/cleanTask'); const watchTask = createWatchTask(choices, { - styleTask: styleTask(choices), - scriptTask: scriptTask(choices), - markupTask: markupTask(choices), - imagesTask + styleTask: styleTask(choices) as TaskFunction, + scriptTask: scriptTask(choices) as TaskFunction, + markupTask: markupTask(choices) as TaskFunction, + imagesTask: imagesTask() as TaskFunction }); - const defaultTask = series( - cleanTask, - parallel( - styleTask(choices), - scriptTask(choices), - markupTask(choices), - imagesTask - ), - browserSyncServe, - watchTask - ); + const defaultTask: TaskFunction = (done) => { + return series( + cleanTask(), + parallel( + styleTask(choices), + scriptTask(choices), + markupTask(choices), + imagesTask() + ), + browserSyncServe, + watchTask + )(done); + }; - const buildTask = series( - cleanTask, - parallel( - styleTask(choices), - scriptTask(choices), - markupTask(choices), - imagesTask - ) - ); + const buildTask: TaskFunction = (done) => { + return series( + cleanTask(), + parallel( + styleTask(choices), + scriptTask(choices), + markupTask(choices), + imagesTask() + ) + )(done); + }; return { defaultTask, buildTask }; } catch (error) { @@ -60,34 +67,44 @@ async function createGulpTasks() { } } -export async function run() { +export async function setup(): Promise { try { - const { defaultTask, buildTask } = await createGulpTasks(); - - if (process.argv.includes('build')) { - buildTask((err?: Error | null) => { - if (err) { - logger.error(`Build failed: ${err.message}`); - } else { - logger.success('Build completed successfully'); - } - }); - } else { - defaultTask((err?: Error | null) => { - if (err) { - logger.error(`Development server failed: ${err.message}`); - } else { - logger.success('Development server started'); - } - }); + 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'); } - } catch (error) { - logger.error(`An error occurred during execution: ${(error as Error).message}`); - process.exit(1); + + 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}`); } } -run().catch(error => { +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/modules/buildEnvironment.ts b/_gulp/modules/buildEnvironment.ts index dedc550..bba6b05 100644 --- a/_gulp/modules/buildEnvironment.ts +++ b/_gulp/modules/buildEnvironment.ts @@ -1,5 +1,5 @@ import { create as createBrowserSync } from 'browser-sync'; -import { series, watch } from 'gulp'; +import { series, TaskFunction, watch } from 'gulp'; import { UserChoices } from '../types'; const bs = createBrowserSync(); @@ -18,13 +18,20 @@ export function browserSyncReload(cb: () => void): void { cb(); } -export function createWatchTask(choices: UserChoices, tasks: Record 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/**/*'; +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)); diff --git a/_gulp/modules/gulpfileGenerator.ts b/_gulp/modules/gulpfileGenerator.ts index c3dac37..5b8f8d2 100644 --- a/_gulp/modules/gulpfileGenerator.ts +++ b/_gulp/modules/gulpfileGenerator.ts @@ -7,21 +7,26 @@ 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 babel = require('gulp-babel'); -const terser = require('gulp-terser'); +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 rimraf = require('rimraf'); // Correct import of rimraf as a function +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'; -function clean(cb) { - rimraf('dist', cb); // Correct usage of rimraf as a function +async function clean() { + await del(['dist']); } function styles() { @@ -37,14 +42,24 @@ function styles() { } function scripts() { - return src('src/${choices.script === 'TypeScript' ? 'ts' : 'js'}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}') - .pipe(plumber()) - .pipe(gulpif(!production, sourcemaps.init())) - ${choices.script === 'TypeScript' - ? '.pipe(typescript())' - : '.pipe(babel({ presets: ["@babel/preset-env"] }))' - } - .pipe(terser()) + 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')); } @@ -97,4 +112,4 @@ exports.default = series(clean, parallel(styles, scripts, markup, images), serve `; writeFile('gulpfile.js', gulpfileContent); -} +} \ No newline at end of file diff --git a/_gulp/tasks/cleanTask.ts b/_gulp/tasks/cleanTask.ts index e56d11e..3a43dfb 100644 --- a/_gulp/tasks/cleanTask.ts +++ b/_gulp/tasks/cleanTask.ts @@ -1,5 +1,7 @@ import del from 'del'; export function cleanTask() { - return del(['dist']); + 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 index 5a60f61..31e27dc 100644 --- a/_gulp/tasks/imagesTask.ts +++ b/_gulp/tasks/imagesTask.ts @@ -1,8 +1,10 @@ -import { dest, src } from 'gulp'; +import { dest, src, TaskFunction } from 'gulp'; import imagemin from 'gulp-imagemin'; -export function imagesTask() { - return src('src/img/**/*') - .pipe(imagemin()) - .pipe(dest('dist/img')); +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 index 55ab577..625af01 100644 --- a/_gulp/tasks/markupTask.ts +++ b/_gulp/tasks/markupTask.ts @@ -4,7 +4,7 @@ import pug from 'gulp-pug'; import { UserChoices } from '../types'; export function markupTask(choices: UserChoices) { - return function () { + return function() { return src(`src/${choices.markup.toLowerCase()}/**/*.${choices.markup === 'Pug' ? 'pug' : 'html'}`) .pipe(plumber()) .pipe(choices.markup === 'Pug' ? pug() : plumber()) diff --git a/_gulp/tasks/scriptTask.ts b/_gulp/tasks/scriptTask.ts index f4b13b5..117c21e 100644 --- a/_gulp/tasks/scriptTask.ts +++ b/_gulp/tasks/scriptTask.ts @@ -1,18 +1,32 @@ -import { dest, src } from 'gulp'; -import babel from 'gulp-babel'; -import plumber from 'gulp-plumber'; +import browserify from 'browserify'; +import { dest, TaskFunction } from 'gulp'; +import rename from 'gulp-rename'; import sourcemaps from 'gulp-sourcemaps'; -import terser from 'gulp-terser'; -import typescript from 'gulp-typescript'; +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) { - return function () { - return src(`src/${choices.script.toLowerCase()}/**/*.${choices.script === 'TypeScript' ? 'ts' : 'js'}`, { sourcemaps: true }) - .pipe(plumber()) - .pipe(choices.script === 'TypeScript' ? typescript() : babel({ presets: ['@babel/preset-env'] })) - .pipe(terser()) - .pipe(sourcemaps.write('.')) +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 index 0e8374c..42b14b0 100644 --- a/_gulp/tasks/styleTask.ts +++ b/_gulp/tasks/styleTask.ts @@ -1,6 +1,6 @@ -import { dest, src } from 'gulp'; +import { dest, src, TaskFunction } from 'gulp'; import autoprefixer from 'gulp-autoprefixer'; -import cssnano from 'gulp-cssnano'; +import cleanCSS from 'gulp-clean-css'; import plumber from 'gulp-plumber'; import gulpSass from 'gulp-sass'; import sourcemaps from 'gulp-sourcemaps'; @@ -9,13 +9,13 @@ import { UserChoices } from '../types'; const sassCompiler = gulpSass(sass); -export function styleTask(choices: UserChoices) { - return function () { +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(cssnano()) + .pipe(cleanCSS()) .pipe(sourcemaps.write('.')) .pipe(dest('dist/css')); }; diff --git a/_gulp/user-choices.json b/_gulp/user-choices.json index de4109e..f65dd01 100644 --- a/_gulp/user-choices.json +++ b/_gulp/user-choices.json @@ -1,5 +1,5 @@ { - "script": "JavaScript", + "script": "TypeScript", "style": "Sass", "markup": "Pug", "addNormalize": true, diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..6498fce --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,107 @@ + +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 typescript = require('gulp-typescript'); +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/package.json b/package.json index d19457e..afef1a6 100644 --- a/package.json +++ b/package.json @@ -6,37 +6,55 @@ "type": "commonjs", "scripts": { "postinstall": "ts-node _gulp/gulpSetup.ts", - "start": "gulp", - "build": "gulp build" + "start": "NODE_OPTIONS=--no-deprecation gulp", + "build": "NODE_OPTIONS=--no-deprecation gulp build" }, "author": "Raniro Coelho", - "license": "ISC", + "license": "MIT", "devDependencies": { - "@babel/core": "^7.22.5", - "@babel/preset-env": "^7.22.5", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", "@types/browser-sync": "^2.27.0", + "@types/browserify": "^12.0.40", "@types/gulp": "^4.0.10", + "@types/gulp-autoprefixer": "^0.0.33", + "@types/gulp-clean-css": "^4.3.4", "@types/gulp-if": "^0.0.34", + "@types/gulp-imagemin": "^7.0.3", + "@types/gulp-rename": "^2.0.1", "@types/gulp-sass": "^5.0.0", + "@types/gulp-sourcemaps": "^0.0.35", + "@types/gulp-uglify": "^3.0.7", "@types/inquirer": "^8.2.5", "@types/node": "^20.3.1", + "@types/pug": "^2.0.10", + "@types/vinyl-buffer": "^1.0.3", + "@types/vinyl-source-stream": "^0.0.34", + "babelify": "^10.0.0", "browser-sync": "^2.29.3", - "del": "^7.0.0", + "browserify": "^17.0.0", + "del": "^6.1.1", "gulp": "^4.0.2", "gulp-autoprefixer": "^8.0.0", - "gulp-babel": "^8.0.0", "gulp-clean-css": "^4.3.0", "gulp-if": "^3.0.0", "gulp-imagemin": "^7.1.0", "gulp-plumber": "^1.2.1", "gulp-pug": "^5.0.0", + "gulp-rename": "^2.0.0", "gulp-sass": "^5.1.0", "gulp-sourcemaps": "^3.0.0", - "gulp-terser": "^2.1.0", "gulp-typescript": "^6.0.0-alpha.1", + "gulp-uglify": "^3.0.2", "inquirer": "^8.2.5", "sass": "^1.63.6", "ts-node": "^10.9.1", - "typescript": "^5.1.3" + "tsify": "^5.0.4", + "typescript": "^5.1.3", + "vinyl-buffer": "^1.0.1", + "vinyl-source-stream": "^2.0.0" + }, + "dependencies": { + "immutable": "^4.3.7" } } diff --git a/tsconfig.json b/tsconfig.json index 3365708..696ec67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,17 +23,18 @@ "moduleResolution": "node", "baseUrl": "./", "paths": { - "*": ["node_modules/*"] + "*": ["node_modules/*", "_gulp/types/*", "./typings"] }, "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "forceConsistentCasingInFileNames": true, - "types": ["node", "gulp", "browser-sync"], - "typeRoots": ["node_modules/@types", "typings"] + "types": ["node", "gulp", "gulp-imagemin", "browserify", "gulp-rename", "gulp-uglify", "vinyl-buffer", "vinyl-source-stream"], + "typeRoots": ["node_modules/@types", "_gulp/types", "./typings"] }, "include": [ - "_gulp/**/*" + "_gulp/**/*", + "_gulp/types/custom.d.ts" ], "exclude": [ "node_modules", diff --git a/typings/custon.d.ts b/typings/custon.d.ts new file mode 100644 index 0000000..3edbec1 --- /dev/null +++ b/typings/custon.d.ts @@ -0,0 +1,6 @@ +declare module 'gulp-imagemin'; +declare module 'browserify'; +declare module 'gulp-rename'; +declare module 'gulp-uglify'; +declare module 'vinyl-buffer'; +declare module 'vinyl-source-stream'; \ No newline at end of file diff --git a/typings/gulp-cssnano.d.ts b/typings/gulp-cssnano.d.ts deleted file mode 100644 index adfaf2a..0000000 --- a/typings/gulp-cssnano.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'gulp-cssnano'; \ No newline at end of file