Skip to content

Instantly share code, notes, and snippets.

@frankyonnetti
Created September 6, 2023 22:07
Show Gist options
  • Save frankyonnetti/e77ae19f2124b693d2f961d19ce9c30a to your computer and use it in GitHub Desktop.
Save frankyonnetti/e77ae19f2124b693d2f961d19ce9c30a to your computer and use it in GitHub Desktop.
> 0.2%
last 2 version
Firefox ESR
not dead
!.eslintrc.json
node_modules
build.env.js

Install

Install the following node modules (npm), in the same directory as this readme file, for compiling and linting our code.

yarn add autoprefixer browser-sync postcss sass sass-migrator standard stylelint stylelint-config-sass-guidelines stylelint-order uglify-js util yarn-upgrade-all --dev

To run sass-migrator on a file, run the following command yarn sass-migrator module --verbose src/sass/DIR/FILE.scss


Config

build.env file:

  • Duplicate build.env.sample.js file and rename it build.env.js.
  • Change values in file as needed.

Linting

We'll also be linting our Sass and Javascript in order to produce well written and consistent code.

Required lint files (should be added during initial theme setup):

  • .eslintrc.json
  • .stylelint.json
  • .stylelintignore

To do a global lint on the SCSS files run: npx stylelint "scss/**/*.scss". To fix found errors add the --fix flag.


Editor

Sublime: install the following packages using package control:

  • SublimeLinter
  • SublimeLinter-contrib-standard
  • SublimeLinter-eslint
  • SublimeLinter-stylelint

VS Code: install the following extensions:

Workspace setting should be located at the root of this project's repo: /.vscode/settings.json

{
  "files.trimTrailingWhitespace": true,
  "eslint.workingDirectories": [
    "htdocs/themes/THEMENAME"
  ],
  "stylelint.configBasedir": "htdocs/themes/THEMENAME",
  "stylelint.configFile": "htdocs/themes/THEMENAME/.stylelintrc.json",
  "stylelint.snippet": [
    "sass",
    "scss"
  ],
  "stylelint.validate": [
    "sass",
    "scss"
  ],
  "phpsab.fixerEnable": true,
  "phpsab.snifferEnable": true,
  "phpsab.standard": "Drupal",
  "phpsab.executablePathCS": "./vendor/bin/phpcs",
  "phpsab.executablePathCBF": "./vendor/bin/phpcbf"
}
/*
| Browser-sync options:
| http://www.browsersync.io/docs/options/
|
| Duplicate file and rename it 'build.env.js'.
| Change values below as needed.
|
*/
module.exports = {
port: 3434,
proxy: 'local.drupal10.test',
open: false,
browser: 'google chrome',
ui: false,
ghostMode: false,
notify: true,
reloadOnRestart: true
}
const fs = require('fs')
const util = require('util')
const autoprefixer = require('autoprefixer')
const browserSync = require('browser-sync').create()
const postcss = require('postcss')
const sass = require('sass')
const uglifyjs = require('uglify-js')
const localenv = require('./build.env')
const writeFile = util.promisify(fs.writeFile)
// paths
const path = {
styles: {
src: 'scss/styles.scss',
srcDir: 'scss',
dest: 'css/styles.css',
watch: 'scss/**/*.scss'
},
scripts: {
src: 'js/scripts.js',
srcFile: 'scripts.js',
dest: 'js/min/scripts.js',
watch: 'js/scripts.js'
}
}
// BrowserSync settings
function bsync () {
browserSync.init({
port: localenv.port,
proxy: localenv.proxy,
open: localenv.open,
browser: localenv.browser,
ui: localenv.iu,
ghostMode: localenv.ghostMode,
notify: localenv.notify,
reloadOnRestart: localenv.reloadOnRestart,
files: [path.styles.dest, path.scripts.dest],
snippetOptions: {
// Load Browsersync inject code before the closing body tag
// in-order to avoid issues with D10's admin toolbar.
rule: {
match: /<\/body>/i,
fn: function (snippet, match) {
return snippet + match
}
}
}
})
}
// Function to compile Sass to CSS
function compileSass () {
const result = sass.compile(path.styles.src, {
style: 'expanded',
sourceMap: true,
sourceMapIncludeSources: true
})
return result
}
// Function to add vendor prefixes to CSS using PostCSS Autoprefixer
async function processCSS (css) {
const result = await postcss([autoprefixer]).process(css, {
from: path.styles.dest,
to: path.styles.dest,
map: {
inline: false, // Generate external sourcemap file
sourcesContent: true
}
})
return result
}
// Main function to compile Sass, add vendor prefixes
async function buildCSS () {
try {
// Compile Sass to CSS
const sassResult = compileSass()
// create embedded sass sourcemap.
const sassMap = JSON.stringify(sassResult.sourceMap)
// convert sources to relative paths.
const mapSources = JSON.parse(sassMap)
const updatedSources = mapSources.sources.map(source => {
const segments = source.split('/')
const srcIndex = segments.indexOf(path.styles.srcDir) // looks for 'src' directory.
if (srcIndex !== -1) {
const remainingSegments = segments.slice(srcIndex)
return remainingSegments.join('/').replace(/^/, '../')
} else {
return source
}
})
mapSources.sources = updatedSources
const updatedSassMap = JSON.stringify(mapSources)
// complete embedded sass sourcemap after resource paths replacement.
const sassMapBase64 = (Buffer.from(updatedSassMap, 'utf8') || '').toString('base64')
const sassMapComment = '/*# sourceMappingURL=data:application/json;charset=utf-8;base64,' + sassMapBase64 + ' */'
const finalResult = sassResult.css.toString() + '\n'.repeat(2) + sassMapComment
// Add vendor prefixes to the CSS using PostCSS Autoprefixer
const processedCss = await processCSS(finalResult)
// Write the processed CSS and sourcemap to files
await writeFile(path.styles.dest, processedCss.css)
await writeFile(path.styles.dest + '.map', processedCss.map.toString())
console.log('Sass build complete!')
} catch (error) {
console.error('Sass build error:', error)
}
}
// Function to compress JavaScript
function compressJS () {
const jsCode = fs.readFileSync(path.scripts.src, 'utf8')
const options = {
compress: true,
mangle: false,
sourceMap: {
filename: path.scripts.srcFile,
url: path.scripts.srcFile + '.map'
}
}
const result = uglifyjs.minify(jsCode, options)
return {
code: result.code,
map: result.map
}
}
function minifyJS () {
try {
// Compress JavaScript using UglifyJS
const jsResult = compressJS()
// Replace "sources" value with file name.
const jsResultMap = JSON.parse(jsResult.map)
const emptySourceValue = '0'
const fileSourceValue = '../' + path.scripts.srcFile
// Find the index of the old value in the sources array
const sourceIndex = jsResultMap.sources.indexOf(emptySourceValue)
// If the old value is found, replace it with the new value
if (sourceIndex !== -1) {
jsResultMap.sources[sourceIndex] = fileSourceValue
}
// Write the compressed JavaScript and sourcemap to files
writeFile(path.scripts.dest, jsResult.code)
writeFile(path.scripts.dest + '.map', JSON.stringify(jsResultMap))
console.log('UglifyJS complete!')
} catch (error) {
console.error('UglifyJS error:', error)
}
}
// ----------------------------------------------------------------------------
// Run BrowserSync
function watchFiles () {
bsync()
browserSync.watch(path.styles.watch).on('change', function () {
buildCSS()
})
browserSync.watch(path.scripts.watch).on('change', function () {
minifyJS()
})
}
module.exports = {
buildCSS,
minifyJS,
watchFiles
}
{
"name": "project-name",
"version": "0.1.0",
"description": "Project description goes here...",
"repository": {
"type": "git",
"url": "https://github.com/repo.git"
},
"author": "DesignHammer, LLC",
"license": "GPL-2.0 AND MIT",
"homepage": "https://designhammer.com",
"scripts": {
"style": "node -e \"require('./build').buildCSS()\"",
"script": "node -e \"require('./build').minifyJS()\"",
"watch": "node -e \"require('./build').watchFiles()\""
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment