UNPKG

sandpaper

Version:

Sandpaper is a commandline tool that's used to smooth out the rough edges when coding websites using HTML, CSS, and Javascript.

709 lines (640 loc) 18.8 kB
/* Tutorials https://www.smashingmagazine.com/2014/06/building-with-gulp/ https://github.com/osscafe/gulp-cheatsheet https://www.youtube.com/watch?v=dwSLFai8ovQ https://css-tricks.com/bem-101/ Useful information https://www.npmjs.com/package/glob // handling files and folders https://semver.org/ // versioning spec https://caniuse.com/#compare https://www.npmjs.com/package/gulp-if https://github.com/jshint/jshint https://www.npmjs.com/package/cssnano https://stylelint.io/ https://github.com/stylelint/stylelint-config-standard https://github.com/hudochenkov/stylelint-order Post CSS information https://github.com/postcss/gulp-postcss https://www.postcss.parts/ https://github.com/postcss/postcss#plugins https://github.com/anandthakker/doiuse https://stylelint.io/user-guide/example-config/ https://github.com/lahmatiy/postcss-csso https://www.npmjs.com/package/gulp-postcss https://github.com/csstools/postcss-preset-env https://github.com/gucong3000/postcss-unprefix https://github.com/postcss/autoprefixer */ const fs = require('fs'); const path = require('path'); const autoprefixer = require('autoprefixer'); const browserSync = require('browser-sync').create(); const cache = require('gulp-cache'); const changed = require('gulp-changed'); const changedInPlace = require('gulp-changed-in-place'); const composer = require('gulp-uglify/composer'); const doiuse = require('doiuse'); const eslint = require('gulp-eslint7'); const gulp = require('gulp'); const gulpif = require('gulp-if'); const cleanCss = require('gulp-clean-css'); const htmlhint = require('gulp-htmlhint'); const htmlmin = require('gulp-htmlmin'); const imagemin = require('gulp-imagemin'); const jshint = require('gulp-jshint'); const postcss = require('gulp-postcss'); const postcsssafeparser = require('postcss-safe-parser'); const postcssColorGuard = require('colorguard'); const postCSSinHTML = require('gulp-html-postcss'); const postcssReporter = require('postcss-reporter'); /* Removed until updated to support PostCss^8.0.0 const postcssNormalize = require('postcss-normalize'); */ const presetEnv = require('postcss-preset-env'); const pump = require('pump'); const purgecss = require('gulp-purgecss'); const stylelint = require('stylelint'); const gulpStylelint = require('gulp-stylelint'); const stylish = require('jshint-more-stylish'); const through2 = require('through2'); const tslint = require('gulp-tslint'); const typescript = require('gulp-typescript'); const uglify = require('uglify-js'); const unprefix = require('postcss-unprefix'); const zip = require('gulp-zip'); const markdownlint = require('markdownlint'); // Unified Engine const unified = require('unified'); const parse = require('rehype-parse'); const stringify = require('rehype-stringify'); const rehype2retext = require('rehype-retext'); const remark2retext = require('remark-retext'); const engine = require('unified-engine-gulp'); // Retext Modules const english = require('retext-english'); const contractions = require('retext-contractions'); const indefinite = require('retext-indefinite-article'); const passive = require('retext-passive'); const quotes = require('retext-quotes'); const repeated = require('retext-repeated-words'); const spacing = require('retext-sentence-spacing'); const redundantAcronyms = require('retext-redundant-acronyms'); const errorsOnlyStylelint = require('./errors-only.stylelintrc.js').config; const codingstyleStylelint = require('./codingstyle.stylelintrc.js').config; let SourceMaps = false; let onlyLintErrors = true; let staging = true; const composerUglify = composer(uglify, console); const currentTime = Date.now; const landingPage = 'index.html'; const ProjectName = 'Sandpaper-Project'; const ZipName = ProjectName + ' - ' + currentTime; const pluginsPostCSS = [ presetEnv({stage: 2 /* Stage 0 - 4 */}), unprefix(), autoprefixer() /* Removed until updated to support PostCss^8.0.0 postcssNormalize({forceImport: 'normalize.css'}) // Breaks CompileHTML */ ]; const unifiedPlugins = [ english, contractions, indefinite, passive, redundantAcronyms, repeated, spacing ]; const uglifyjsOptions = {}; const workingDirectory = 'src'; const paths = { // This will likely need to be updated for your project. src: { all: workingDirectory + '/**/*', html: workingDirectory + '/**/*.htm*', css: workingDirectory + '/**/*.css', js: workingDirectory + '/**/*.js', ts: workingDirectory + '/**/*.ts', php: workingDirectory + '/**/*.php', md: workingDirectory + '/**/*.md', images: workingDirectory + '/**/*.+(png|jpg|gif|svg)', leftovers: '!' + workingDirectory + '/**/*.+(htm*|css|js|ts|png|jpg|gif|svg)' // All files that aren't of the mime type listed above. }, dev: { all: 'dev', html: 'dev', css: 'dev', js: 'dev', ts: 'dev', php: 'dev', md: 'dev', images: 'dev' }, prod: { all: 'dist', html: 'dist', css: 'dist', js: 'dist', ts: 'dist', php: 'dist', md: 'dist', images: 'dist' }, report: { css: 'report/css' }, index: landingPage, // The .html file to open when running gulp sync. sourcemaps: './sourcemaps' }; function getEslintConfigPath() { const fileLoc1 = path.resolve('./', 'node_modules', 'sandpaper', 'eslint.config.js'); const fileLoc2 = path.resolve('eslint.config.js'); /* No need to test both paths */ /* istanbul ignore next */ return fs.existsSync(fileLoc2) ? fileLoc2.toString() : fileLoc1.toString(); } function releaseMode() { staging = false; SourceMaps = false; return Promise.resolve('staging'); } function errorLint() { onlyLintErrors = true; return Promise.resolve('errorlint'); } async function strictLint() { onlyLintErrors = false; return Promise.resolve('fullLint'); } function includeSourceMaps() { staging = true; SourceMaps = true; return Promise.resolve('SourceMaps'); } function getPath(condition) { /* No need to test both paths */ /* istanbul ignore next */ return (condition ? paths.dev.all : paths.prod.all); } async function clean(cb) { const path = getPath(staging); cache.clearAll(); fs.rmdir(path, {recursive: true}, cb => { /* istanbul ignore next */ return cb; }); return cb; } function logWriter(error) { if (error === '' || error === undefined || error === null) { error = 'Empty Error Message'; } if (!fs.existsSync('./logs/')) { fs.mkdirSync('./logs/'); } const logfilelocation = `./logs/errorlog_${Date.now()}.txt`; console.log(logfilelocation); fs.writeFile(logfilelocation, error.toString(), err => { /* istanbul ignore next */ if (err) { throw err; } return console.log('Details written to logfile: ' + path.resolve(logfilelocation)); }); return Promise.resolve(); } function copyAssets() { return gulp.src([paths.src.all, paths.src.leftovers]) // All files that aren't of the mime type listed in paths.rel.leftovers .pipe(gulp.dest(gulpif(staging, paths.dev.all, paths.prod.all))); } // From https://github.com/doshprompt/htmlhint-stylish/issues/1#issuecomment-251012229 function htmlReporter(file) { return stylish.reporter(file.htmlhint.messages.map(errMessage => ({ file: path.relative(file.cwd, errMessage.file), error: { character: errMessage.error.col, code: errMessage.error.rule.id, line: errMessage.error.line, reason: errMessage.error.message } }))); } function compileCSS() { return gulp.src(paths.src.css, {sourcemaps: SourceMaps}) .pipe(changed(gulpif(staging, paths.dev.css, paths.prod.css))) .pipe(postcss(pluginsPostCSS)) .pipe(purgecss({content: [paths.src.html]})) .pipe(cleanCss({level: 2})) .pipe(gulp.dest(gulpif(staging, paths.dev.css, paths.prod.css), {sourcemaps: paths.sourcemaps})) .pipe(browserSync.stream()); } function compileHTML() { return (async () => { pump([ gulp.src(paths.src.html, {sourcemaps: SourceMaps}), changed(gulpif(staging, paths.dev.html, paths.prod.html)), postCSSinHTML(pluginsPostCSS), htmlmin({ collapseInlineTagWhitespace: true, collapseWhitespace: true, minifyCSS: true, minifyJS: true, minifyURLs: true, removeComments: true, html5: true, useShortDoctype: true }), gulp.dest(gulpif(staging, paths.dev.html, paths.prod.html), {sourcemaps: paths.sourcemaps}) ], onError => { /* There is no else statement, therefor it will be ignored. */ /* istanbul ignore else */ if (onError !== undefined) { logWriter(onError.message); console.log( 'CompileHTML experienced an error. \n', 'The most common cause is a Parse error, fixing linting errors usually fixes parse errors.' ); linthtml(); } } ); browserSync.stream(); })(); } function compileTS() { return gulp.src(paths.src.ts, {sourcemaps: SourceMaps}) .pipe(typescript( { /* Options that are currently unused experimentalAsyncFunctions: true, target: 'ES6' // 'ES3' (default), 'ES5' or 'ES6'. */ })) .pipe(gulp.dest(gulpif(staging, paths.dev.js, paths.prod.js), {sourcemaps: paths.sourcemaps})) .pipe(browserSync.stream()); } function uglifyjs(cb) { return pump([ gulp.src(paths.src.js, {sourcemaps: SourceMaps}), changed(gulpif(staging, paths.dev.js, paths.prod.js)), composerUglify(uglifyjsOptions), gulp.dest(gulpif(staging, paths.dev.js, paths.prod.js), {sourcemaps: paths.sourcemaps}) ], browserSync.stream(), cb ); } function optamizeImages() { return gulp.src(paths.src.images) .pipe(changed(gulpif(staging, paths.dev.html, paths.prod.html))) .pipe(cache(imagemin([ imagemin.gifsicle({interlaced: false}, {optimizationLevel: gulpif(staging, 1, 3)}), // Optimization level can be 1 - 3 imagemin.mozjpeg({progressive: false}), imagemin.optipng({optimizationLevel: gulpif(staging, 1, 7)}), // OptimizationLevel: 0 - 7 (5 is a good tradeoff if 7 takes too long.) imagemin.svgo({ plugins: [ {cleanupAttrs: true}, {removeDoctype: true}, {removeXMLProcInst: true}, {removeComments: true}, {removeMetadata: true}, {removeTitle: true}, {removeDesc: true}, {removeUselessDefs: true}, {removeXMLNS: false}, // Setting this to true will break the image {removeEditorsNSData: true}, {removeEmptyAttrs: true}, {removeHiddenElems: true}, {removeEmptyText: true}, {removeEmptyContainers: true}, {removeViewBox: true}, {cleanupEnableBackground: true}, {minifyStyles: true}, {convertStyleToAttrs: true}, {convertColors: true}, {convertPathData: true}, {convertTransform: true}, {removeUnknownsAndDefaults: true}, {removeNonInheritableGroupAttrs: true}, {removeUselessStrokeAndFill: true}, {removeUnusedNS: true}, {cleanupIDs: true}, {cleanupNumericValues: true}, {cleanupListOfValues: true}, {moveElemsAttrsToGroup: true}, {moveGroupAttrsToElems: true}, {collapseGroups: true}, {removeRasterImages: true}, {mergePaths: true}, {convertShapeToPath: true} ] }) /* Should look into using pngout again at some point. */ // imageminPngout() ], {verbose: true}))) .pipe(gulp.dest(gulpif(staging, paths.dev.images, paths.prod.images))); } function linthtml() { if (onlyLintErrors) { return gulp.src(paths.src.html) .pipe(htmlhint()) .pipe(htmlhint.reporter(htmlReporter)); } // Else return gulp.src(paths.src.html) .pipe(htmlhint({ 'alt-require': true, 'attr-lowercase': true, 'attr-no-duplication': true, 'attr-unsafe-chars': true, 'attr-value-double-quotes': true, 'attr-value-not-empty': true, 'doctype-first': true, 'doctype-html5': true, 'head-script-disabled': true, 'href-abs-or-rel': false, 'id-class-ad-disabled': true, 'id-unique': true, 'inline-script-disabled': false, 'inline-style-disabled': false, 'space-tab-mixed-disabled': 'tab', 'spec-char-escape': true, 'src-not-empty': true, 'style-disabled': false, 'tag-pair': true, 'tag-self-close': true, 'tagname-lowercase': true, 'title-require': true })) .pipe(htmlhint.reporter(htmlReporter)); } function lintcss() { const pluginsPostCSSlint = () => { return onlyLintErrors ? [ stylelint({config: errorsOnlyStylelint}), postcssReporter({clearReportedMessages: true}) ] : [ stylelint({config: codingstyleStylelint}), doiuse({/* Options */}), postcssColorGuard({threshold: 3}), postcssReporter({clearReportedMessages: true}) ]; }; return gulp.src(paths.src.css) .pipe(postcss(pluginsPostCSSlint, {parser: postcsssafeparser})); } function lintjs() { if (onlyLintErrors) { return gulp.src(paths.src.js) .pipe(jshint()) .pipe(jshint.reporter(stylish)); } try { return gulp.src(paths.src.js) .pipe(eslint({configFile: getEslintConfigPath()})) .pipe(eslint.format('stylish')); } catch (error) { /* istanbul ignore next */ console.log(error); } } function lintts() { return gulp.src(paths.src.ts) .pipe(tslint({formatter: 'stylish'})) .pipe(tslint.report({emitError: false})); } function lintmd() { return gulp.src(paths.src.md, {read: false}) .pipe(through2.obj((file, enc, next) => { markdownlint( {files: [workingDirectory + '/' + file.relative]}, (err, result) => { console.dir(result, {colors: true, depth: null}); next(err, file); }); })); } function lintmarkdownContent() { return gulp.src(paths.src.md) .pipe( engine({name: 'gulp-unified', processor: require('unified')})() .use(parse) .use( remark2retext, unified() .use([unifiedPlugins]) .use(quotes, {preferred: 'straight'}) ) .use(stringify) ); } function lintHtmlContent() { return gulp.src(paths.src.html) .pipe( engine({name: 'gulp-unified', processor: require('unified')})() .use(parse) .use( rehype2retext, unified() .use([unifiedPlugins]) .use(quotes, {preferred: 'straight'}) ) .use(stringify) ); } function zipSrc() { return gulp.src(paths.src.all) .pipe(zip(ZipName)) .pipe(gulp.dest(paths.prod.all)); } function fixjs() { try { return gulp.src(paths.src.js) .pipe(eslint({configFile: getEslintConfigPath(), fix: true})) .pipe(gulpif(isFixed, gulp.dest(workingDirectory))); } catch (error) { /* istanbul ignore next */ console.log(error); } function isFixed(file) { return file.eslint !== null && file.eslint.fixed; } } function fixcss() { return gulp.src(paths.src.css) .pipe(changedInPlace()) .pipe(gulpStylelint({ reporters: [{formatter: 'verbose', console: false}], failAfterError: false, config: codingstyleStylelint, fix: true })) .pipe(gulp.dest(workingDirectory)); } function watchfix() { gulp.watch(paths.src.css, fixcss); gulp.watch(paths.src.js, fixjs); } function watch() { gulp.watch(paths.src.html, compileHTML); gulp.watch(paths.src.css, compileCSS); gulp.watch(paths.src.js, uglifyjs); gulp.watch(paths.src.ts, compileTS); gulp.watch(paths.src.images, optamizeImages); gulp.watch(paths.src.leftovers, copyAssets); console.log('Build Watch running, Waiting for file changes.'); console.log('Press Ctrl + C to end a file watch.'); } function watchlint() { gulp.watch(paths.src.html, {delay: 500}, linthtml); gulp.watch(paths.src.css, {delay: 500}, lintcss); gulp.watch(paths.src.js, {delay: 500}, lintjs); gulp.watch(paths.src.ts, {delay: 500}, lintts); console.log('Lint Watch running, Waiting for file changes.'); console.log('Press Ctrl + C to end a file watch.'); } function syncBrowsers() { const baseDir = getPath(staging); browserSync.init({ server: { baseDir, index: paths.index } }); gulp.watch(paths.src.html, compileHTML).on('change', browserSync.reload); gulp.watch(paths.src.css, compileCSS).on('change', browserSync.reload); gulp.watch(paths.src.js, uglifyjs).on('change', browserSync.reload); gulp.watch(paths.src.ts, compileTS).on('change', browserSync.reload); gulp.watch(paths.src.images, optamizeImages).on('change', browserSync.reload); gulp.watch(paths.src.leftovers, copyAssets).on('change', browserSync.reload); } function syncStop() { browserSync.exit(); } exports.clean = clean; exports.compileCSS = compileCSS; exports.compileHTML = compileHTML; exports.compileTS = compileTS; exports.copyAssets = copyAssets; exports.htmlReporter = htmlReporter; exports.includeSourceMaps = includeSourceMaps; exports.logWriter = logWriter; exports.lintcss = lintcss; exports.linthtml = linthtml; exports.lintjs = lintjs; exports.lintts = lintts; exports.lintmd = lintmd; exports.lintmarkdownContent = lintmarkdownContent; exports.lintHtmlContent = lintHtmlContent; exports.optamizeImages = optamizeImages; exports.releaseMode = releaseMode; exports.strictLint = strictLint; exports.errorLint = errorLint; exports.syncBrowsers = syncBrowsers; exports.uglifyjs = uglifyjs; exports.watch = watch; exports.watchlint = watchlint; exports.zipSrc = zipSrc; exports.fixjs = fixjs; exports.fixcss = fixcss; exports.watchfix = watchfix; // Combined functions / jobs const lint = gulp.series( linthtml, lintcss, lintjs, lintts, lintmd, lintmarkdownContent ); const batchEverthing = gulp.parallel( compileCSS, compileHTML, uglifyjs, compileTS, optamizeImages, copyAssets ); const buildDev = gulp.series( includeSourceMaps, batchEverthing ); const buildProd = gulp.series( releaseMode, batchEverthing ); const watchDev = gulp.series( includeSourceMaps, batchEverthing, watch ); const watchProd = gulp.series( releaseMode, batchEverthing, watch ); const lintStrict = gulp.series( strictLint, lint, lintHtmlContent ); const lintErrors = gulp.series( errorLint, lint, lintHtmlContent ); const syncDev = gulp.series( includeSourceMaps, batchEverthing, syncBrowsers ); const syncProd = gulp.series( releaseMode, batchEverthing, syncBrowsers ); const watchLint = gulp.series( errorLint, lint, watchlint ); const watchLintStrict = gulp.series( strictLint, lint, watchlint ); const fixSrc = gulp.series( strictLint, fixjs, fixcss ); const autoFixSrc = gulp.series( strictLint, fixjs, fixcss, watchfix ); exports.buildDev = buildDev; exports.buildProd = buildProd; exports.watchDev = watchDev; exports.watchProd = watchProd; exports.lint = lint; exports.lintErrors = lintErrors; exports.lintStrict = lintStrict; exports.syncDev = syncDev; exports.syncProd = syncProd; exports.syncStop = syncStop; exports.watchLint = watchLint; exports.watchLintStrict = watchLintStrict; exports.fixSrc = fixSrc; exports.autoFixSrc = autoFixSrc; gulp.task('default', buildDev); gulp.task('prod', buildProd); gulp.task('lint', lintErrors); gulp.task('lintstrict', lintStrict); gulp.task('zip', zipSrc); gulp.task('syncDev', syncDev); gulp.task('syncProd', syncProd); gulp.task('watchlint', watchLint); gulp.task('watchlintstrict', watchLintStrict);