Skip to content

Commit

Permalink
Merge pull request #56 from softlimit/cwc/parent-theme
Browse files Browse the repository at this point in the history
  • Loading branch information
itsanolive committed Apr 26, 2023
2 parents 387a71e + 08274f2 commit 82bec1b
Show file tree
Hide file tree
Showing 31 changed files with 932 additions and 310 deletions.
49 changes: 34 additions & 15 deletions build/functions/get-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,86 +11,105 @@
*/
const path = require('path')
const glob = require('glob')
const parentThemeFiles = require('./parent-theme-files')
const parentThemeFiles = require('./parent-theme/parent-theme-files')

// ignore node_modules in globs
const globSync = (src, pattern) => glob.sync(path.resolve(src, pattern), {
ignore: {
ignored: p => {
// ignore node_modules in parent theme but not the parent theme itself (within node_modules)
if (ThemeEnvy.parentTheme) {
return p.fullpath().includes(path.resolve(ThemeEnvy.parentTheme.path, 'node_modules')) ? true : p.fullpath().includes('node_modules') && !p.fullpath().includes(ThemeEnvy.parentTheme.path)
}
return p.fullpath().includes('node_modules')
},
}
})

const globs = {
assets: {
glob(src) {
return glob.sync(path.resolve(src, '**/assets/**/*'))
return globSync(src, '**/assets/**/*')
},
},
config: {
glob(src) {
return glob.sync(path.resolve(src, '**/config/**/*.js'))
return globSync(src, '**/config/**/*.js')
},
filter: file => path.basename(file) !== 'settings_schema.js',
},
criticalCSS: {
glob(src) {
return glob.sync(path.resolve(src, '**/critical.css'))
return globSync(src, '**/critical.css')
},
},
elements: {
glob(src) {
return [...glob.sync(path.resolve(src, '**/elements/**/index.js')), ...glob.sync(path.resolve(src, '**/elements/*.js'))]
return [...globSync(src, '**/elements/**/index.js'), ...globSync(src, '**/elements/*.js')]
}
},
features: {
glob(src) {
return glob.sync(path.resolve(src, 'theme-envy/features/**/index.js'))
return globSync(src, '**/features/**/index.js')
},
},
installs: {
glob(src) {
return glob.sync(path.resolve(src, '**/install.js'))
return globSync(src, '**/install.js')
},
},
liquid: {
glob(src) {
return glob.sync(path.resolve(src, '**/*.liquid'))
return globSync(src, '**/*.liquid')
},
filter: file => !file.includes('partials'),
},
locales: {
glob(src) {
return globSync(src, '**/locales/**/*.json')
}
},
partials: {
glob(src) {
return glob.sync(path.resolve(src, '**/partials/**/*.liquid'))
return globSync(src, '**/partials/**/*.liquid')
},
},
schema: {
glob(src) {
return glob.sync(path.resolve(src, '**/schema/**/*.js'))
return globSync(src, '**/schema/**/*.js')
},
},
sectionGroups: {
glob(src) {
return glob.sync(path.resolve(src, '**/sections/**/*.json'))
return globSync(src, '**/sections/**/*.json')
},
},
snippets: {
glob(src) {
return glob.sync(path.resolve(src, '**/snippets/**/*.liquid'))
return globSync(src, '**/snippets/**/*.liquid')
},
},
templates: {
glob(src) {
return glob.sync(path.resolve(src, '**/templates/**/*.json'))
return globSync(src, '**/templates/**/*.json')
},
},
}

module.exports = function(type) {
function getFiles(src, only) {
// src is either the themePath or the parentTheme
// only is a list of directory names to filter against, used for parentTheme
// only is a list of directory paths to filter against, used for parentTheme
let files = globs[type].glob(src)
if (only) {
files = files.filter(file => {
return only.some(dir => file.indexOf(dir) > -1)
return only.some(dir => file.includes(`${dir}/`) || file.includes(`${dir}.js`))
})
}
if (globs[type].filter) {
files = files.filter(globs[type].filter)
}

return files
}
const files = getFiles(ThemeEnvy.themePath)
Expand Down
1 change: 0 additions & 1 deletion build/functions/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module.exports = {
getAll: require('./get-all'),
liquid: require('./liquid'),
tailwind: require('./tailwind'),
themeEnvy: require('./theme-envy'),
webpack: require('./webpack'),
}
2 changes: 1 addition & 1 deletion build/functions/liquid/functions/list-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const listDependencies = ({ filePath, source }) => {
const action = tag[2]
const name = tag[3]
if (action === 'partial') {
const file = globbedPartials.filter(partial => partial.includes(`/${`${name}.liquid`}`))
const file = globbedPartials.filter(partial => path.basename(partial) === `${name}.liquid`)
// if partialPath doesn't return anything, exit process and output error
if (file.length === 0) {
console.log(`\n${logSymbols.error} ${chalk.red.bold('Error:')}\n\n${chalk.red(`${name}.liquid`)} partial file not found, referenced in:\n${chalk.dim.underline(filePath)}\n\nTo resolve, confirm the partial file exists and that the file\nname reference in the {% partial %} tag matches the partial file.\n`)
Expand Down
48 changes: 44 additions & 4 deletions build/functions/liquid/functions/section-schema-inject.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
/**
* @file Prebuild helper script that will inject the schema js file into the corresponding sections/*.liquid file
* also injects installs for blocks/section schema
* @example
* // include the schema in the section liquid file
* {% schema 'schema-file.js' %}
*/
const path = require('path')
const { parseSchema } = require('#Helpers')

module.exports = function({ source, filePath }) {
if (!filePath.includes('sections')) return source
const schema = source.match(/{% schema '(.*)' %}/g) || source.match(/{% schema "(.*)" %}/g)
// if there are no schema tags with a filename string, return
if (!schema) return source
// inject installs into inlined schema with {% schema %} {% endschema %} tags
const hasSchemaTag = source.match(/{% schema %}/g)
if (hasSchemaTag) return injectInstallsSchema({ source, filePath })
// inject schema from file into schema with {% schema 'filename.js' %} tags
const hasJsSchema = source.match(/{% schema '(.*)' %}/g) || source.match(/{% schema "(.*)" %}/g)
if (hasJsSchema) return injectJsSchema({ source, filePath, schema: hasJsSchema })
// if neither of the above...
return source
}

function injectJsSchema({ source, filePath, schema }) {
// regexp for a quoted string within our schema match
const schemaFile = schema[0].match(/'(.*)'/)[1] || schema[0].match(/"(.*)"/)[1]
// load the file export
const schemaSource = ThemeRequire(schemaFile, { loader: filePath })
// check for installs
const schemaWithInjections = checkInstalls({ schema: schemaSource, filePath })
// replace the {% schema %} tag with the schema string and update asset
return source.replace(schema[0], formatSchema(schemaSource))
return source.replace(schema[0], formatSchema(schemaWithInjections))
}

function injectInstallsSchema({ source, filePath }) {
// split our schema tag from the rest of the file
const { schema, schemaStart, schemaEnd } = parseSchema(source)
const schemaWithInjections = checkInstalls({ schema, filePath })
// replace everything between schemaStart and schemaEnd with the new schema
source = source.replace(source.substring(schemaStart, schemaEnd + String('{% endschema %}').length), formatSchema(schemaWithInjections))
return source
}

function checkInstalls({ schema, filePath }) {
// check our schema from install.js files and inject into schema
const sectionName = `${path.basename(path.dirname(filePath))}/${path.basename(filePath)}`
if (!ThemeEnvy.schema || !ThemeEnvy.schema[sectionName]) return schema

if (ThemeEnvy.schema[sectionName].settings) {
schema.settings = schema.settings || []
schema.settings = [...schema.settings, ...ThemeEnvy.schema[sectionName].settings]
}

if (ThemeEnvy.schema[sectionName].blocks) {
schema.blocks = schema.blocks || []
schema.blocks = [...schema.blocks, ...ThemeEnvy.schema[sectionName].blocks]
}

return schema
}

function formatSchema(schema) {
Expand Down
25 changes: 0 additions & 25 deletions build/functions/parent-theme-files.js

This file was deleted.

77 changes: 77 additions & 0 deletions build/functions/parent-theme/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @private
* @file Checks for a parent theme in node_modules or relative path and sets ThemeEnvy.parentTheme to the path
*/

const fs = require('fs-extra')
const path = require('path');

(() => {
const ThemeConfigPath = path.resolve(process.cwd(), 'theme.config.js')
if (!fs.existsSync(ThemeConfigPath)) return
ThemeEnvy.childPreferred = ThemeEnvy.childPreferred || []

// look for parent theme in node_modules
const parentThemePath = ThemeEnvy.parentTheme?.path
if (!parentThemePath) {
removeParentThemeReference()
return
}
const parentThemePkg = fs.existsSync(path.resolve(process.cwd(), 'node_modules', parentThemePath))
? path.resolve(process.cwd(), 'node_modules', parentThemePath)
: false
// look for parent theme in relative path
const parentThemeRelative = fs.existsSync(path.resolve(process.cwd(), parentThemePath))
? path.resolve(process.cwd(), parentThemePath)
: false
// prefer node_modules over relative path
const parentTheme = parentThemePkg || parentThemeRelative

if (!parentTheme) {
removeParentThemeReference()
return
}
ThemeEnvy.parentTheme.path = parentTheme
// define elements and features arrays
ThemeEnvy.parentTheme.elements = listFeaturesOrElements({ type: 'elements' })
ThemeEnvy.parentTheme.features = listFeaturesOrElements({ type: 'features' })
})()

function removeParentThemeReference() {
delete ThemeEnvy.parentTheme
}

function listFeaturesOrElements({ type }) {
// get parentTheme config
const parentConfig = require(path.resolve(ThemeEnvy.parentTheme.path, 'parent.config.js'))
// get parentTheme theme-envy directory path
const parentThemeEnvyDir = path.resolve(ThemeEnvy.parentTheme.path, 'src/theme-envy')

const elementsOrFeatures = fs.readdirSync(path.resolve(parentThemeEnvyDir, type))
.filter(file => {
// remove items that are in the parent.config.js exclude array
if (!parentConfig[type]?.exclude) return true
return parentConfig[type].exclude.includes(path.parse(file).name) === false
})
.filter(file => {
// remove items that are in theme.config.js exclude array
if (!ThemeEnvy.parentTheme[type]?.exclude) return true
return ThemeEnvy.parentTheme[type]?.exclude.includes(path.parse(file).name) === false
})
.map(file => path.resolve(parentThemeEnvyDir, type, file).replace('.js', ''))

// add items that are in the include array of the child theme
if (ThemeEnvy.parentTheme[type]?.include) {
ThemeEnvy.parentTheme[type].include.forEach(item => {
if (!elementsOrFeatures.includes(item)) {
// check if the item exists in the parent theme
if (!fs.existsSync(path.resolve(parentThemeEnvyDir, type, item))) {
console.error(`The ${item} ${type} does not exist in the parent theme`)
return
}
elementsOrFeatures.push(path.resolve(ThemeEnvy.parentTheme.path, 'theme-envy', type, item))
}
})
}
return elementsOrFeatures
}
47 changes: 47 additions & 0 deletions build/functions/parent-theme/parent-theme-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Get all files from the parent theme that are not in the child
* @private
* @param {function} func - function to get files with a glob, should return an array of file paths
* @param {array} childFiles - array of file paths from the child theme that we check against
* @returns {array} - array of file paths from the parent theme that are not in the child
*/
const path = require('path')
const { directories } = require('#EnsureDirectories')
// sets resolved parent theme path
require('./index.js')

module.exports = (func, childFiles, type) => {
const childRelative = childFiles.map(file => path.relative(ThemeEnvy.themePath, file))
// get all files from the parent theme, using only elements and features directories in parent theme
let only = [...ThemeEnvy.parentTheme.elements, ...ThemeEnvy.parentTheme.features]

if (type === 'elements') {
only = ThemeEnvy.parentTheme.elements
}
if (type === 'features') {
only = ThemeEnvy.parentTheme.features
}
if (type === 'schema') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/theme-envy/schema'))
}
if (type === 'liquid') {
only.push(...directories.map(dir => path.resolve(ThemeEnvy.parentTheme.path, 'src', dir)))
}
if (type === 'sectionGroups') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/sections'))
}
if (type === 'config') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/config'))
}
if (type === 'locales') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/locales'))
}
if (type === 'templates') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/templates'))
}
if (type === 'criticalCSS') {
only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/styles'))
}

return func(ThemeEnvy.parentTheme.path, only).filter(file => !childRelative.includes(path.relative(ThemeEnvy.parentTheme.path, file)))
}
31 changes: 0 additions & 31 deletions build/functions/tailwind.js

This file was deleted.

0 comments on commit 82bec1b

Please sign in to comment.