Skip to content

Instantly share code, notes, and snippets.

@tmslnz
Last active October 26, 2021 02:30
Show Gist options
  • Save tmslnz/1d025baaa7557a2d994032aa88fb61b3 to your computer and use it in GitHub Desktop.
Save tmslnz/1d025baaa7557a2d994032aa88fb61b3 to your computer and use it in GitHub Desktop.
Complete example gulpfile.js for https://github.com/tmslnz/gulp-shopify-theme
/*
Streamlined Shopify theme development.
NOTE: depends on module gulp-shopify-theme
npm install --save-dev yargs gulp gulp-sass gulp-changed gulp-sourcemaps gulp-autoprefixer gulp-uglify gulp-concat gulp-replace gulp-plumber gulp-babel browser-sync gulp-if del gulp-add-src gulp-rename gulp-yaml gulp-shopify-theme
Highlights:
- https proxying via BrowserSync
- autoreload
- sourcemaps support
- YAML support for Shopify config/ and locales/ files
*/
const argv = require('yargs').argv;
const gulp = require( 'gulp' );
const sass = require( 'gulp-sass' );
const changed = require( 'gulp-changed' );
const sourcemaps = require( 'gulp-sourcemaps' );
const uglify = require( 'gulp-uglify' );
const concat = require( 'gulp-concat' );
const replace = require( 'gulp-replace' );
const plumber = require( 'gulp-plumber' );
const babel = require( 'gulp-babel' );
const browsersync = require( 'browser-sync' ).create();
const gulpif = require( 'gulp-if' );
const del = require( 'del' );
const addsrc = require( 'gulp-add-src' );
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const rename = require( 'gulp-rename' );
const yaml = require( 'gulp-yaml' );
const jsyaml = require( 'js-yaml' );
const theme = require( 'gulp-shopify-theme' ).create();
const shopifyconfig = require( './~shopifyconfig.json' );
var DESTINATION = argv.dest || 'dist';
var USE_JS_UGLIFY = !!(argv.uglify || process.env.USE_JS_UGLIFY);
var USE_SOURCEMAPS = !(argv.nomaps || process.env.DISABLE_SOURCEMAPS);
var USE_BROWSER_SYNC = !!(argv.browsersync || argv.bs || process.env.USE_BROWSER_SYNC);
var BROWSER_SYNC_PORT = parseInt(argv.browsersync) || parseInt(argv.bs) || parseInt(process.env.BROWSER_SYNC_PORT) || '3000';
const sourceMappingURLCSSregExp = new RegExp('(.*?[/*]{2,}# sourceMappingURL=)(.*?)([/*]{2})', 'g');
const sourceMappingURLJSregExp = new RegExp('(.*?[/*]{2,}# sourceMappingURL=)(.*?)', 'g');
const sourceMappingURLCSSreplace = '{% raw %}$1{% endraw %}$2{% raw %}$3{% endraw %}';
const sourceMappingURLJSreplace = '{% raw %}$1{% endraw %}$2';
shopifyconfig.root = process.cwd() + '/' + DESTINATION;
gulp.task( 'default', [ 'css', 'js', 'js-libs', 'fonts', 'images', 'copy', 'configs' ] );
gulp.task( 'dev', [ 'theme', 'default', 'browsersync', 'watch' ] );
gulp.task( 'clean', function () {
return del(DESTINATION);
});
gulp.task( 'purge', [ 'theme' ], function (done) {
theme.purge();
done();
});
gulp.task( 'theme', function () {
theme.init( shopifyconfig );
});
gulp.task( 'watch', function () {
USE_JS_UGLIFY = false;
// Watch & run tasks
gulp.watch( 'src/assets/{css,css-libs}/**/*.{css,less,scss,liquid}', [ 'reload-on-css' ] );
gulp.watch( 'src/assets/js/**/*.js', [ 'reload-on-js' ] );
gulp.watch( 'src/assets/js-libs/**/*.js', [ 'reload-on-js-libs' ] );
gulp.watch( 'src/assets/fonts/**/*', [ 'reload-on-fonts' ] );
gulp.watch( 'src/assets/images/**/*', [ 'reload-on-images' ] );
gulp.watch( 'src/{layout,config,snippets,sections,templates,locales}/**/*', [ 'reload-on-copy' ]);
});
gulp.task( 'css', function () {
return gulp.src( 'src/assets/css/all.scss' )
.pipe( plumber() )
.pipe( sourcemaps.init() )
.pipe( sass().on('error', sass.logError) )
.pipe( replace( /({{|}}|{%|%})/g, '/*!$1*/' ) ) // Comment out Liquid tags, so post-css doesn't trip out
.pipe( postcss( [
autoprefixer({browsers: [ 'last 2 versions', 'Explorer >= 9' ]}),
] ) )
.pipe( replace( /\/\*!({{|}}|{%|%})\*\//g, '$1' ) ) // Re-enable Liquid tags
.pipe( rename( 'css_all.css' ) )
.pipe( sourcemaps.write('.', {sourceMappingURL: makeLiquidSourceMappingURL})) // css_all.css.map
.pipe( rename(appendLiquidExt)) // css_all.css.liquid
.pipe( replace( sourceMappingURLCSSregExp, sourceMappingURLCSSreplace ) )
.pipe( gulp.dest( DESTINATION + '/assets' ) )
.pipe( theme.stream() );
});
gulp.task( 'js', function () {
return gulp.src( [ 'src/assets/js/!(main)*.js', 'src/assets/js/main.js' ] )
.pipe( plumber() )
.pipe( sourcemaps.init() )
.pipe( babel({presets: ['es2015']}) )
.pipe( concat( 'js_script.js' ) )
.pipe( gulpif( USE_JS_UGLIFY, uglify() ) )
.pipe( sourcemaps.write('.', {sourceMappingURL: makeLiquidSourceMappingURL}))
.pipe( rename(appendLiquidExt))
.pipe( replace( sourceMappingURLJSregExp, sourceMappingURLJSreplace ) )
.pipe( gulp.dest( DESTINATION + '/assets' ) )
.pipe( theme.stream() );
});
gulp.task( 'js-libs', function () {
return gulp.src( [ 'src/assets/js-libs/jquery.js' , 'src/assets/js-libs/*.js' ] )
.pipe( plumber() )
.pipe( concat( 'js_libs.js' ) )
.pipe( gulpif( USE_JS_UGLIFY, uglify() ) )
.pipe( gulp.dest( DESTINATION + '/assets' ) )
.pipe( theme.stream() );
});
gulp.task( 'fonts', function () {
return gulp.src( [ 'src/assets/fonts/**/*.{ttf,woff,woff2,eof,eot,otf,svg}' ] )
.pipe( plumber() )
.pipe( changed( DESTINATION, {hasChanged: changed.compareSha1Digest} ) )
.pipe( rename( flatten ))
.pipe( rename({ dirname: '', prefix: 'fonts_' }))
.pipe( gulp.dest( DESTINATION + '/assets' ) )
.pipe( theme.stream() );
});
gulp.task( 'images', function () {
return gulp.src( [ 'src/assets/images/**/*.{svg,png,jpg,jpeg,gif,ico}', '!src/assets/images/src/**/*' ] )
.pipe( plumber() )
.pipe( changed( DESTINATION, {hasChanged: changed.compareSha1Digest} ) )
.pipe( rename( flatten ))
.pipe( rename({ dirname: '', prefix: 'images_' }))
.pipe( gulp.dest( DESTINATION + '/assets' ) )
.pipe( theme.stream() );
});
gulp.task( 'copy', function () {
return gulp.src( [ 'src/{layout,snippets,templates,sections}/**/*.*' ] )
.pipe( plumber() )
.pipe( replace( /{% schema %}([^]*.+[^]*){% endschema %}/gi, replaceYAMLwithJSON ) )
.pipe( replace(/({%)(?!\s*?(?:end)?(?:raw|schema|javascript|stylesheet)\s*?)(.+?)(%})/g, '$1- $2 -$3') ) // make whitespace-insensitive tags {% -> {%-
.pipe( replace( /^\s*[\r\n]/gm, '' ) ) // remove empty lines
.pipe( changed( DESTINATION, {hasChanged: changed.compareSha1Digest} ) )
.pipe( gulp.dest( DESTINATION ) )
.pipe( theme.stream() );
});
gulp.task( 'configs', function () {
return gulp.src( [ 'src/{config,locales}/**/*.*' ] )
.pipe( plumber() )
.pipe( yaml({space: 2}) )
.pipe( changed( DESTINATION, {hasChanged: changed.compareSha1Digest} ) )
.pipe( gulp.dest( DESTINATION ) )
.pipe( theme.stream() );
});
gulp.task('reload-on-css', ['css'], reload);
gulp.task('reload-on-js', ['js'], reload);
gulp.task('reload-on-js-libs', ['js-libs'], reload);
gulp.task('reload-on-fonts', ['fonts'], reload);
gulp.task('reload-on-images', ['images'], reload);
gulp.task('reload-on-copy', ['copy', 'configs'], reload);
function reload (done) {
if (!USE_BROWSER_SYNC) return done();
browsersync.reload(); done();
}
gulp.task( 'browsersync', function (done) {
if (!USE_BROWSER_SYNC) return done();
browsersync.init({
port: BROWSER_SYNC_PORT, ui: { port: BROWSER_SYNC_PORT + 1 },
proxy: 'https://'+ shopifyconfig.shop_name +'.myshopify.com',
browser: [],
notify: false,
startPath: "/?preview_theme_id=" + shopifyconfig.theme_id,
}, done);
});
console.log('DESTINATION', DESTINATION);
console.log('USE_JS_UGLIFY', USE_JS_UGLIFY);
console.log('USE_SOURCEMAPS', USE_SOURCEMAPS);
console.log('USE_BROWSER_SYNC', USE_BROWSER_SYNC);
console.log('BROWSER_SYNC_PORT', BROWSER_SYNC_PORT);
function replaceYAMLwithJSON (match, g1) {
if (match) {
var yamlString = g1.replace(/{% (end)?schema %}/, '');
var parsedYaml = jsyaml.safeLoad(yamlString);
var jsonString = JSON.stringify(parsedYaml, null, ' ');
return '{% schema %}\n' + jsonString + '\n{% endschema %}';
}
}
function makeLiquidSourceMappingURL (file) {
return '{{"' + file.relative + '.map" | asset_url }}';
}
function appendLiquidExt (path) {
if (path.extname === '.map') return;
if (path.extname === '.css') {
path.extname = '.scss';
}
path.basename += path.extname;
path.extname = '.liquid';
}
function flatten (path) {
if (path.dirname !== '.') {
path.basename = path.dirname.replace('/', '_') + '_' + path.basename;
}
}
@superfein
Copy link

Thank you so much for sharing your updated code! I'm excited to start using this workflow, and totally understand you're very busy and don't have time to update the node module and Github gist.

I have a few questions due to errors that came up:

  1. These two lines:
    SHOPIFY_CONFIG.theme_id = PACKAGE.shopify_dev.theme_id;
    SHOPIFY_CONFIG.shop_name = PACKAGE.shopify_dev.shop_name;
    ...where is shopify_dev defined? Is that an addition you made to the node module that hasn't been published? It's referenced several times in the gulpfile.js code you shared, but it causes errors in the console when I try to run "gulp dev". - this line as well:
    proxy: PACKAGE.shopify_dev.shop_url,

  2. This line:
    const shopifyUtil = require( 'gulp-shopify-theme' ).util;
    ...is this .util function part of the current gulp-shopify-theme module? I can see it's being used throughout the gulpfile.js, but is causing errors in the console.

It's used in the css() and js() functions:
.pipe( rename(shopifyUtil.appendLiquidExt)) // css_all.css.liquid
.pipe( replace( shopifyUtil.sourceMappingURLCSSregExp, shopifyUtil.sourceMappingURLCSSreplace ) )

This line is from the font() function:
.pipe( rename( shopifyUtil.flatten ))

This line is from the copy() function:
.pipe( replace( /{% schema %}([^]*.+[^]*){% endschema %}/gi, shopifyUtil.replaceYAMLwithJSON ) )

Everything else seems to be working, at least as far as I can tell from the command line.

Lastly, just double checking I did this right since it isn't mentioned in your Github repo docs:
I created this file ~shopifyconfig.json in the root and put all the sensitive shop related info in there, like api_key, shop_name etc.

Thanks again Tommaso!

@jack-fdrv
Copy link

jack-fdrv commented Apr 17, 2020

assert.js:374
throw err;
^

AssertionError [ERR_ASSERTION]: Task function must be specified
at Gulp.set [as _setTask] (dev/node_modules/undertaker/lib/set-task.js:10:3)
at Gulp.task (dev/node_modules/undertaker/lib/task.js:13:8)
at Object. (dev/gulpfile.js:44:6)
at Module._compile (internal/modules/cjs/loader.js:959:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
at Module.load (internal/modules/cjs/loader.js:815:32)
at Function.Module._load (internal/modules/cjs/loader.js:727:14)
at Module.require (internal/modules/cjs/loader.js:852:19)
at require (internal/modules/cjs/helpers.js:74:18)
at execute (/usr/local/lib/node_modules/gulp-cli/lib/versioned/^4.0.0/index.js:36:18) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '=='

@tmslnz
Copy link
Author

tmslnz commented Apr 17, 2020 via email

@jack-fdrv
Copy link

That you have to create yourself with the API secrets

I think the problem in version of gulp.

@jack-fdrv
Copy link

[22:37:52] gulp-postcss: revolution.settings.css
autoprefixer: dev/assets/revolution.settings.css:478:3: Gradient has outdated direction syntax. New syntax is like to left instead of right.
Error in plugin "sass"
Message:
assets/awemenu.css.liquid
Error: Invalid CSS after " top: 0;": expected "}", was "{% if settings.enab"
on line 378 of assets/awemenu.css.liquid

top: 0;

---------^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment