@softlimit/theme-envy 0.1.3-alpha → 0.1.4-alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. package/build/functions/get-all.js +34 -15
  2. package/build/functions/index.js +0 -1
  3. package/build/functions/liquid/functions/list-dependencies.js +1 -1
  4. package/build/functions/liquid/functions/section-schema-inject.js +44 -4
  5. package/build/functions/parent-theme/index.js +77 -0
  6. package/build/functions/parent-theme/parent-theme-files.js +47 -0
  7. package/build/functions/webpack-plugins/retry-chunk-load-plugin.js +88 -0
  8. package/build/functions/webpack.js +24 -1
  9. package/build/index.js +3 -4
  10. package/build/requires/assets.js +2 -3
  11. package/build/requires/globals/index.js +0 -1
  12. package/build/requires/globals/prepare-install-hooks-schema.js +1 -2
  13. package/build/requires/globals/progress-bar.js +1 -2
  14. package/build/requires/globals/theme-require.js +11 -9
  15. package/build/requires/locales.js +13 -8
  16. package/build/requires/scripts/script-builders/elements.build.js +29 -21
  17. package/build/requires/styles/styles-builders/theme-envy.css.js +5 -1
  18. package/build/theme-envy.config.js +18 -7
  19. package/helpers/functions/parse-schema.js +20 -0
  20. package/helpers/functions/pull-json.js +1 -1
  21. package/helpers/index.js +1 -0
  22. package/index.js +1 -0
  23. package/init/functions/copy-starter-config-files/configs/postcss.config.js +7 -7
  24. package/init/functions/copy-starter-config-files/configs/tailwind.config.js +18 -4
  25. package/init/functions/copy-starter-config-files/configs/theme.config.js +1 -1
  26. package/package.json +5 -5
  27. package/build/functions/parent-theme-files.js +0 -25
  28. package/build/functions/tailwind.js +0 -31
  29. package/build/requires/globals/parent-theme.js +0 -28
  30. package/build/requires/styles/tailwind-base.css +0 -3
@@ -11,69 +11,87 @@
11
11
  */
12
12
  const path = require('path')
13
13
  const glob = require('glob')
14
- const parentThemeFiles = require('./parent-theme-files')
14
+ const parentThemeFiles = require('./parent-theme/parent-theme-files')
15
+
16
+ // ignore node_modules in globs
17
+ const globSync = (src, pattern) => glob.sync(path.resolve(src, pattern), {
18
+ ignore: {
19
+ ignored: p => {
20
+ // ignore node_modules in parent theme but not the parent theme itself (within node_modules)
21
+ if (ThemeEnvy.parentTheme) {
22
+ return p.fullpath().includes(path.resolve(ThemeEnvy.parentTheme.path, 'node_modules')) ? true : p.fullpath().includes('node_modules') && !p.fullpath().includes(ThemeEnvy.parentTheme.path)
23
+ }
24
+ return p.fullpath().includes('node_modules')
25
+ },
26
+ }
27
+ })
15
28
 
16
29
  const globs = {
17
30
  assets: {
18
31
  glob(src) {
19
- return glob.sync(path.resolve(src, '**/assets/**/*'))
32
+ return globSync(src, '**/assets/**/*')
20
33
  },
21
34
  },
22
35
  config: {
23
36
  glob(src) {
24
- return glob.sync(path.resolve(src, '**/config/**/*.js'))
37
+ return globSync(src, '**/config/**/*.js')
25
38
  },
26
39
  filter: file => path.basename(file) !== 'settings_schema.js',
27
40
  },
28
41
  criticalCSS: {
29
42
  glob(src) {
30
- return glob.sync(path.resolve(src, '**/critical.css'))
43
+ return globSync(src, '**/critical.css')
31
44
  },
32
45
  },
33
46
  elements: {
34
47
  glob(src) {
35
- return [...glob.sync(path.resolve(src, '**/elements/**/index.js')), ...glob.sync(path.resolve(src, '**/elements/*.js'))]
48
+ return [...globSync(src, '**/elements/**/index.js'), ...globSync(src, '**/elements/*.js')]
36
49
  }
37
50
  },
38
51
  features: {
39
52
  glob(src) {
40
- return glob.sync(path.resolve(src, 'theme-envy/features/**/index.js'))
53
+ return globSync(src, '**/features/**/index.js')
41
54
  },
42
55
  },
43
56
  installs: {
44
57
  glob(src) {
45
- return glob.sync(path.resolve(src, '**/install.js'))
58
+ return globSync(src, '**/install.js')
46
59
  },
47
60
  },
48
61
  liquid: {
49
62
  glob(src) {
50
- return glob.sync(path.resolve(src, '**/*.liquid'))
63
+ return globSync(src, '**/*.liquid')
51
64
  },
52
65
  filter: file => !file.includes('partials'),
53
66
  },
67
+ locales: {
68
+ glob(src) {
69
+ return globSync(src, '**/locales/**/*.json')
70
+ }
71
+ },
54
72
  partials: {
55
73
  glob(src) {
56
- return glob.sync(path.resolve(src, '**/partials/**/*.liquid'))
74
+ return globSync(src, '**/partials/**/*.liquid')
57
75
  },
58
76
  },
59
77
  schema: {
60
78
  glob(src) {
61
- return glob.sync(path.resolve(src, '**/schema/**/*.js'))
79
+ return globSync(src, '**/schema/**/*.js')
62
80
  },
63
81
  },
64
82
  sectionGroups: {
65
83
  glob(src) {
66
- return glob.sync(path.resolve(src, '**/sections/**/*.json'))
84
+ return globSync(src, '**/sections/**/*.json')
67
85
  },
68
86
  },
69
87
  snippets: {
70
88
  glob(src) {
71
- return glob.sync(path.resolve(src, '**/snippets/**/*.liquid'))
89
+ return globSync(src, '**/snippets/**/*.liquid')
72
90
  },
73
91
  },
74
92
  templates: {
75
93
  glob(src) {
76
- return glob.sync(path.resolve(src, '**/templates/**/*.json'))
94
+ return globSync(src, '**/templates/**/*.json')
77
95
  },
78
96
  },
79
97
  }
@@ -81,16 +99,17 @@ const globs = {
81
99
  module.exports = function(type) {
82
100
  function getFiles(src, only) {
83
101
  // src is either the themePath or the parentTheme
84
- // only is a list of directory names to filter against, used for parentTheme
102
+ // only is a list of directory paths to filter against, used for parentTheme
85
103
  let files = globs[type].glob(src)
86
104
  if (only) {
87
105
  files = files.filter(file => {
88
- return only.some(dir => file.indexOf(dir) > -1)
106
+ return only.some(dir => file.includes(`${dir}/`) || file.includes(`${dir}.js`))
89
107
  })
90
108
  }
91
109
  if (globs[type].filter) {
92
110
  files = files.filter(globs[type].filter)
93
111
  }
112
+
94
113
  return files
95
114
  }
96
115
  const files = getFiles(ThemeEnvy.themePath)
@@ -1,7 +1,6 @@
1
1
  module.exports = {
2
2
  getAll: require('./get-all'),
3
3
  liquid: require('./liquid'),
4
- tailwind: require('./tailwind'),
5
4
  themeEnvy: require('./theme-envy'),
6
5
  webpack: require('./webpack'),
7
6
  }
@@ -22,7 +22,7 @@ const listDependencies = ({ filePath, source }) => {
22
22
  const action = tag[2]
23
23
  const name = tag[3]
24
24
  if (action === 'partial') {
25
- const file = globbedPartials.filter(partial => partial.includes(`/${`${name}.liquid`}`))
25
+ const file = globbedPartials.filter(partial => path.basename(partial) === `${name}.liquid`)
26
26
  // if partialPath doesn't return anything, exit process and output error
27
27
  if (file.length === 0) {
28
28
  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`)
@@ -1,21 +1,61 @@
1
1
  /**
2
2
  * @file Prebuild helper script that will inject the schema js file into the corresponding sections/*.liquid file
3
+ * also injects installs for blocks/section schema
3
4
  * @example
4
5
  * // include the schema in the section liquid file
5
6
  * {% schema 'schema-file.js' %}
6
7
  */
8
+ const path = require('path')
9
+ const { parseSchema } = require('#Helpers')
7
10
 
8
11
  module.exports = function({ source, filePath }) {
9
12
  if (!filePath.includes('sections')) return source
10
- const schema = source.match(/{% schema '(.*)' %}/g) || source.match(/{% schema "(.*)" %}/g)
11
- // if there are no schema tags with a filename string, return
12
- if (!schema) return source
13
+ // inject installs into inlined schema with {% schema %} {% endschema %} tags
14
+ const hasSchemaTag = source.match(/{% schema %}/g)
15
+ if (hasSchemaTag) return injectInstallsSchema({ source, filePath })
16
+ // inject schema from file into schema with {% schema 'filename.js' %} tags
17
+ const hasJsSchema = source.match(/{% schema '(.*)' %}/g) || source.match(/{% schema "(.*)" %}/g)
18
+ if (hasJsSchema) return injectJsSchema({ source, filePath, schema: hasJsSchema })
19
+ // if neither of the above...
20
+ return source
21
+ }
22
+
23
+ function injectJsSchema({ source, filePath, schema }) {
13
24
  // regexp for a quoted string within our schema match
14
25
  const schemaFile = schema[0].match(/'(.*)'/)[1] || schema[0].match(/"(.*)"/)[1]
15
26
  // load the file export
16
27
  const schemaSource = ThemeRequire(schemaFile, { loader: filePath })
28
+ // check for installs
29
+ const schemaWithInjections = checkInstalls({ schema: schemaSource, filePath })
17
30
  // replace the {% schema %} tag with the schema string and update asset
18
- return source.replace(schema[0], formatSchema(schemaSource))
31
+ return source.replace(schema[0], formatSchema(schemaWithInjections))
32
+ }
33
+
34
+ function injectInstallsSchema({ source, filePath }) {
35
+ // split our schema tag from the rest of the file
36
+ const { schema, schemaStart, schemaEnd } = parseSchema(source)
37
+ const schemaWithInjections = checkInstalls({ schema, filePath })
38
+ // replace everything between schemaStart and schemaEnd with the new schema
39
+ source = source.replace(source.substring(schemaStart, schemaEnd + String('{% endschema %}').length), formatSchema(schemaWithInjections))
40
+ return source
41
+ }
42
+
43
+ function checkInstalls({ schema, filePath }) {
44
+ // check our schema from install.js files and inject into schema
45
+ const sectionName = `${path.basename(path.dirname(filePath))}/${path.basename(filePath)}`
46
+ if (!ThemeEnvy.schema || !ThemeEnvy.schema[sectionName]) return schema
47
+
48
+ if (ThemeEnvy.schema[sectionName].settings) {
49
+ schema.settings = schema.settings || []
50
+ schema.settings = [...schema.settings, ...ThemeEnvy.schema[sectionName].settings]
51
+ }
52
+
53
+ if (ThemeEnvy.schema[sectionName].blocks) {
54
+ schema.blocks = schema.blocks || []
55
+ schema.blocks = [...schema.blocks, ...ThemeEnvy.schema[sectionName].blocks]
56
+ }
57
+
58
+ return schema
19
59
  }
20
60
 
21
61
  function formatSchema(schema) {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @private
3
+ * @file Checks for a parent theme in node_modules or relative path and sets ThemeEnvy.parentTheme to the path
4
+ */
5
+
6
+ const fs = require('fs-extra')
7
+ const path = require('path');
8
+
9
+ (() => {
10
+ const ThemeConfigPath = path.resolve(process.cwd(), 'theme.config.js')
11
+ if (!fs.existsSync(ThemeConfigPath)) return
12
+ ThemeEnvy.childPreferred = ThemeEnvy.childPreferred || []
13
+
14
+ // look for parent theme in node_modules
15
+ const parentThemePath = ThemeEnvy.parentTheme?.path
16
+ if (!parentThemePath) {
17
+ removeParentThemeReference()
18
+ return
19
+ }
20
+ const parentThemePkg = fs.existsSync(path.resolve(process.cwd(), 'node_modules', parentThemePath))
21
+ ? path.resolve(process.cwd(), 'node_modules', parentThemePath)
22
+ : false
23
+ // look for parent theme in relative path
24
+ const parentThemeRelative = fs.existsSync(path.resolve(process.cwd(), parentThemePath))
25
+ ? path.resolve(process.cwd(), parentThemePath)
26
+ : false
27
+ // prefer node_modules over relative path
28
+ const parentTheme = parentThemePkg || parentThemeRelative
29
+
30
+ if (!parentTheme) {
31
+ removeParentThemeReference()
32
+ return
33
+ }
34
+ ThemeEnvy.parentTheme.path = parentTheme
35
+ // define elements and features arrays
36
+ ThemeEnvy.parentTheme.elements = listFeaturesOrElements({ type: 'elements' })
37
+ ThemeEnvy.parentTheme.features = listFeaturesOrElements({ type: 'features' })
38
+ })()
39
+
40
+ function removeParentThemeReference() {
41
+ delete ThemeEnvy.parentTheme
42
+ }
43
+
44
+ function listFeaturesOrElements({ type }) {
45
+ // get parentTheme config
46
+ const parentConfig = require(path.resolve(ThemeEnvy.parentTheme.path, 'parent.config.js'))
47
+ // get parentTheme theme-envy directory path
48
+ const parentThemeEnvyDir = path.resolve(ThemeEnvy.parentTheme.path, 'src/theme-envy')
49
+
50
+ const elementsOrFeatures = fs.readdirSync(path.resolve(parentThemeEnvyDir, type))
51
+ .filter(file => {
52
+ // remove items that are in the parent.config.js exclude array
53
+ if (!parentConfig[type]?.exclude) return true
54
+ return parentConfig[type].exclude.includes(path.parse(file).name) === false
55
+ })
56
+ .filter(file => {
57
+ // remove items that are in theme.config.js exclude array
58
+ if (!ThemeEnvy.parentTheme[type]?.exclude) return true
59
+ return ThemeEnvy.parentTheme[type]?.exclude.includes(path.parse(file).name) === false
60
+ })
61
+ .map(file => path.resolve(parentThemeEnvyDir, type, file).replace('.js', ''))
62
+
63
+ // add items that are in the include array of the child theme
64
+ if (ThemeEnvy.parentTheme[type]?.include) {
65
+ ThemeEnvy.parentTheme[type].include.forEach(item => {
66
+ if (!elementsOrFeatures.includes(item)) {
67
+ // check if the item exists in the parent theme
68
+ if (!fs.existsSync(path.resolve(parentThemeEnvyDir, type, item))) {
69
+ console.error(`The ${item} ${type} does not exist in the parent theme`)
70
+ return
71
+ }
72
+ elementsOrFeatures.push(path.resolve(ThemeEnvy.parentTheme.path, 'theme-envy', type, item))
73
+ }
74
+ })
75
+ }
76
+ return elementsOrFeatures
77
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Get all files from the parent theme that are not in the child
3
+ * @private
4
+ * @param {function} func - function to get files with a glob, should return an array of file paths
5
+ * @param {array} childFiles - array of file paths from the child theme that we check against
6
+ * @returns {array} - array of file paths from the parent theme that are not in the child
7
+ */
8
+ const path = require('path')
9
+ const { directories } = require('#EnsureDirectories')
10
+ // sets resolved parent theme path
11
+ require('./index.js')
12
+
13
+ module.exports = (func, childFiles, type) => {
14
+ const childRelative = childFiles.map(file => path.relative(ThemeEnvy.themePath, file))
15
+ // get all files from the parent theme, using only elements and features directories in parent theme
16
+ let only = [...ThemeEnvy.parentTheme.elements, ...ThemeEnvy.parentTheme.features]
17
+
18
+ if (type === 'elements') {
19
+ only = ThemeEnvy.parentTheme.elements
20
+ }
21
+ if (type === 'features') {
22
+ only = ThemeEnvy.parentTheme.features
23
+ }
24
+ if (type === 'schema') {
25
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/theme-envy/schema'))
26
+ }
27
+ if (type === 'liquid') {
28
+ only.push(...directories.map(dir => path.resolve(ThemeEnvy.parentTheme.path, 'src', dir)))
29
+ }
30
+ if (type === 'sectionGroups') {
31
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/sections'))
32
+ }
33
+ if (type === 'config') {
34
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/config'))
35
+ }
36
+ if (type === 'locales') {
37
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/locales'))
38
+ }
39
+ if (type === 'templates') {
40
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/templates'))
41
+ }
42
+ if (type === 'criticalCSS') {
43
+ only.push(path.resolve(ThemeEnvy.parentTheme.path, 'src/styles'))
44
+ }
45
+
46
+ return func(ThemeEnvy.parentTheme.path, only).filter(file => !childRelative.includes(path.relative(ThemeEnvy.parentTheme.path, file)))
47
+ }
@@ -0,0 +1,88 @@
1
+ const prettier = require('prettier')
2
+ const { RuntimeGlobals } = require('webpack')
3
+
4
+ const pluginName = 'RetryChunkLoadPlugin'
5
+
6
+ class RetryChunkLoadPlugin {
7
+ constructor(options = {}) {
8
+ this.options = Object.assign({}, options)
9
+ }
10
+
11
+ apply(compiler) {
12
+ compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
13
+ const { mainTemplate, runtimeTemplate } = compilation
14
+ const maxRetryValueFromOptions = Number(this.options.maxRetries)
15
+ const maxRetries =
16
+ Number.isInteger(maxRetryValueFromOptions) &&
17
+ maxRetryValueFromOptions > 0
18
+ ? maxRetryValueFromOptions
19
+ : 1
20
+ // const getCacheBustString = () =>
21
+ // this.options.cacheBust
22
+ // ? `
23
+ // (${this.options.cacheBust})();
24
+ // `
25
+ // : '"cache-bust=true"'
26
+ mainTemplate.hooks.localVars.tap(
27
+ { name: pluginName, stage: 1 },
28
+ (source, chunk) => {
29
+ const currentChunkName = chunk.name
30
+ const addRetryCode =
31
+ !this.options.chunks ||
32
+ this.options.chunks.includes(currentChunkName)
33
+ if (!addRetryCode) return source
34
+ const script = runtimeTemplate.iife(
35
+ '',
36
+ `
37
+ if(typeof ${RuntimeGlobals.require} !== "undefined") {
38
+ var oldGetScript = ${RuntimeGlobals.getChunkScriptFilename};
39
+ var oldLoadScript = ${RuntimeGlobals.ensureChunk};
40
+ var queryMap = new Map();
41
+ var countMap = new Map();
42
+ ${RuntimeGlobals.getChunkScriptFilename} = function(chunkId){
43
+ var result = oldGetScript(chunkId);
44
+ return result + (queryMap.has(chunkId) ? '?' + queryMap.get(chunkId) : '');
45
+ };
46
+ ${RuntimeGlobals.ensureChunk} = function(chunkId){
47
+ var result = oldLoadScript(chunkId);
48
+ return result.catch(function(error){
49
+ var retries = countMap.has(chunkId) ? countMap.get(chunkId) : ${maxRetries};
50
+ if (retries < 1) {
51
+ var realSrc = oldGetScript(chunkId);
52
+ error.message = 'Loading chunk ' + chunkId + ' failed after ${maxRetries} retries.\\n(' + realSrc + ')';
53
+ error.request = realSrc;${
54
+ this.options.lastResortScript
55
+ ? this.options.lastResortScript
56
+ : ''
57
+ }
58
+ throw error;
59
+ }
60
+ return new Promise(function (resolve) {
61
+ setTimeout(function () {
62
+ var retryAttempt = ${maxRetries} - retries + 1;
63
+ var retryAttemptString = '&retry-attempt=' + retryAttempt;
64
+ const cacheBust = Date.now();
65
+ queryMap.set(chunkId, cacheBust);
66
+ countMap.set(chunkId, retries - 1);
67
+ resolve(${RuntimeGlobals.ensureChunk}(chunkId));
68
+ }, ${this.options.retryDelay || this.options.timeout || 0})
69
+ })
70
+ });
71
+ };
72
+ }`
73
+ )
74
+ return (
75
+ source +
76
+ prettier.format(script, {
77
+ trailingComma: 'es5',
78
+ singleQuote: true,
79
+ parser: 'babel'
80
+ })
81
+ )
82
+ }
83
+ )
84
+ })
85
+ }
86
+ }
87
+
88
+ module.exports.RetryChunkLoadPlugin = RetryChunkLoadPlugin
@@ -8,6 +8,7 @@
8
8
  * @returns {Promise}
9
9
  */
10
10
 
11
+ const path = require('path')
11
12
  const webpack = require('webpack')
12
13
  const getAll = require('./get-all')
13
14
 
@@ -25,12 +26,34 @@ module.exports = function({ mode, opts }) {
25
26
  webpackConfig.watch = watch
26
27
  // merge our theme config named entries into webackConfig.entry
27
28
  webpackConfig.entry = { ...webpackConfig.entry, ...ThemeEnvy.entry }
29
+ // merge our theme config resolve aliases into webpackConfig.resolve.alias
30
+ if (ThemeEnvy.resolve?.alias) webpackConfig.resolve.alias = { ...webpackConfig.resolve.alias, ...ThemeEnvy.resolve.alias }
28
31
 
32
+ if (ThemeEnvy?.tailwind !== false) {
33
+ // if tailwind is enabled, we need to add our critical css to the entry list
34
+ webpackConfig.entry['theme-envy.critical'] = [`${ThemeEnvy.paths.build}/requires/styles/theme-envy.css`]
35
+ }
29
36
  if (ThemeEnvy?.tailwind === false && getAll('criticalCSS').length > 0) {
30
37
  // if tailwind is disabled, we need to add our critical css to the entry list
31
- webpackConfig.entry['theme-envy.critical'] = `${ThemeEnvy.paths.build}/requires/styles/theme-envy.css`
38
+ webpackConfig.entry['theme-envy.critical'] = [`${ThemeEnvy.paths.build}/requires/styles/theme-envy.css`]
32
39
  }
33
40
 
41
+ // check for Aliases and Entries in parent theme parent.config.js and merge them with our own
42
+ if (ThemeEnvy.parentTheme) {
43
+ const parentThemeConfig = require(path.resolve(ThemeEnvy.parentTheme.path, 'parent.config.js'))
44
+ // merge parent theme aliases into webpackConfig.resolve.alias
45
+ if (parentThemeConfig.resolve?.alias) webpackConfig.resolve.alias = { ...webpackConfig.resolve.alias, ...parentThemeConfig.resolve.alias }
46
+ // iterate over parent theme entries and add them to webpackConfig.entry, merging with our own into an array if necessary
47
+ if (parentThemeConfig.entry) {
48
+ for (const [key, value] of Object.entries(parentThemeConfig.entry)) {
49
+ if (webpackConfig.entry[key]) {
50
+ webpackConfig.entry[key] = [...webpackConfig.entry[key], ...value]
51
+ } else {
52
+ webpackConfig.entry[key] = [...value]
53
+ }
54
+ }
55
+ }
56
+ }
34
57
  webpack(webpackConfig, (err, stats) => {
35
58
  if (err || stats.hasErrors()) {
36
59
  console.log(stats, err)
package/build/index.js CHANGED
@@ -15,14 +15,15 @@
15
15
  const path = require('path')
16
16
  const chalk = require('chalk')
17
17
  const emoji = require('node-emoji')
18
- const { themeEnvy, webpack, tailwind } = require('#Build/functions')
18
+ const { themeEnvy, webpack } = require('#Build/functions')
19
19
  const { distClean } = require('#Helpers')
20
20
 
21
21
  module.exports = async function(env, opts = {}) {
22
22
  const mode = env || 'production'
23
+ process.env.mode = mode
23
24
 
24
25
  // empty dist folder for a clean build, do not log the clean message
25
- distClean({ quiet: true })
26
+ if (opts.clean || !opts.watch) distClean({ quiet: true })
26
27
 
27
28
  // our pretty build message
28
29
  const relativeDistPath = path.relative(process.cwd(), ThemeEnvy.outputPath)
@@ -37,8 +38,6 @@ module.exports = async function(env, opts = {}) {
37
38
 
38
39
  await themeEnvy({ mode, opts })
39
40
 
40
- if (ThemeEnvy?.tailwind !== false) await tailwind({ mode, opts })
41
-
42
41
  await webpack({ mode, opts })
43
42
 
44
43
  ThemeEnvy.events.emit('build:complete')
@@ -4,9 +4,8 @@
4
4
 
5
5
  const fs = require('fs-extra')
6
6
  const path = require('path')
7
- const glob = require('glob')
8
-
9
- const assets = glob.sync(path.resolve(ThemeEnvy.themePath, '**/assets/**/*.*'))
7
+ const { getAll } = require('#Build/functions')
8
+ const assets = getAll('assets')
10
9
 
11
10
  // if dist/assets doesn't exist create it
12
11
  fs.ensureDirSync(path.resolve(ThemeEnvy.outputPath, 'assets'))
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  require('./progress-bar')
7
- require('./parent-theme')
8
7
  require('./theme-require')
9
8
  require('./prepare-install-hooks-schema')
10
9
  ThemeEnvy.progress.increment('globals')
@@ -43,7 +43,6 @@ const path = require('path');
43
43
  }
44
44
  })
45
45
 
46
- // TODO: LOOK INTO THIS
47
46
  schema.forEach(entry => {
48
47
  const file = `${entry.file}.liquid`
49
48
  ThemeEnvy.schema[file] = ThemeEnvy.schema[file] || {
@@ -53,6 +52,6 @@ const path = require('path');
53
52
  // add the schema to the destination, process entry.content for softlimit partials
54
53
  // target is either 'settings' or 'blocks'
55
54
  const target = entry.target.split('.')[1]
56
- ThemeEnvy.schema[file][target].push(entry.content)
55
+ ThemeEnvy.schema[file][target] = [...ThemeEnvy.schema[file][target], ...entry.content]
57
56
  })
58
57
  })()
@@ -16,10 +16,9 @@ const colors = require('ansi-colors');
16
16
  failedHookInstalls: 1,
17
17
  globals: 1,
18
18
  liquid: getAll('liquid').length,
19
- locales: 1,
19
+ locales: getAll('locales').length,
20
20
  requires: 7,
21
21
  sectionGroups: 1,
22
- tailwind: ThemeEnvy?.tailwind !== false ? 1 : 0,
23
22
  templates: getAll('templates').length,
24
23
  webpack: 1,
25
24
  }
@@ -33,7 +33,9 @@ ThemeEnvy.events.on('watch:start', () => {
33
33
  })
34
34
 
35
35
  const ThemeRequire = (file, options) => {
36
- // check for file in our pre-globbed arrays
36
+ // assume files are javascript if no extension given
37
+ if (!path.extname(file)) file = `${file}.js`
38
+
37
39
  const ref = getFile(file)
38
40
 
39
41
  if (ref.length === 0) {
@@ -87,10 +89,10 @@ const ThemeRequire = (file, options) => {
87
89
  content = schemaExtend(content, options.extend)
88
90
  }
89
91
  if (options?.loop) {
90
- content = schemaLoop(file, content, options.loop)
92
+ content = schemaLoop(content, options.loop)
91
93
  }
92
94
  if (options?.suffix) {
93
- content = schemaSuffix(file, content, options.suffix)
95
+ content = schemaSuffix(content, options.suffix)
94
96
  }
95
97
 
96
98
  if (options?.loader) {
@@ -101,7 +103,7 @@ const ThemeRequire = (file, options) => {
101
103
  return content
102
104
  }
103
105
 
104
- function schemaDelete(file, src, del) {
106
+ function schemaDelete(src, del) {
105
107
  const replaceWith = src.filter(entry => {
106
108
  if (entry.settings) {
107
109
  entry.settings = schemaDelete(entry.settings, del)
@@ -130,7 +132,7 @@ function schemaExtend(src, exts) {
130
132
  return replaceWith
131
133
  }
132
134
 
133
- function schemaLoop(file, src, loop) {
135
+ function schemaLoop(src, loop) {
134
136
  const replaceWith = []
135
137
  for (let i = 1; i < (loop + 1); i++) {
136
138
  const srcCopy = parseJson(src)
@@ -145,7 +147,7 @@ function schemaLoop(file, src, loop) {
145
147
  return replaceWith
146
148
  }
147
149
 
148
- function schemaSuffix(file, src, suffix) {
150
+ function schemaSuffix(src, suffix) {
149
151
  const replaceWith = parseJson(src)
150
152
  replaceWith.map(entry => {
151
153
  if (entry.id) entry.id = `${entry.id}_${suffix}`
@@ -165,8 +167,8 @@ function getFile(file) {
165
167
  return getSchemaFile(file)
166
168
  } else {
167
169
  const res = glob.sync(path.resolve(ThemeEnvy.themePath, `**/${file}`))
168
- if (ThemeEnvy.parentTheme && res.length === 0) {
169
- res.push(...glob.sync(path.resolve(ThemeEnvy.parentTheme, `**/${file}`)))
170
+ if (ThemeEnvy.parentTheme.path && res.length === 0) {
171
+ res.push(...glob.sync(path.resolve(ThemeEnvy.parentTheme.path, `**/${file}`)))
170
172
  }
171
173
  return res
172
174
  }
@@ -176,7 +178,7 @@ function getFile(file) {
176
178
  @param src [object] - the file contents to be extended
177
179
  @param schemaRef [string] - source schema file name being extended
178
180
  */
179
- function parseJson(src, schemaRef) {
181
+ function parseJson(src) {
180
182
  try {
181
183
  return JSON.parse(JSON.stringify(src))
182
184
  } catch (error) {
@@ -3,14 +3,19 @@
3
3
  */
4
4
  const fs = require('fs-extra')
5
5
  const path = require('path')
6
+ const { getAll } = require('#Build/functions')
6
7
 
7
- // if dist/locales does not exist, create it
8
+ const locales = getAll('locales')
9
+
10
+ // if dist/locales doesn't exist create it
8
11
  fs.ensureDirSync(path.resolve(ThemeEnvy.outputPath, 'locales'))
9
12
 
10
- try {
11
- fs.copySync(path.resolve(ThemeEnvy.themePath, 'locales'), path.resolve(ThemeEnvy.outputPath, 'locales'))
12
- // update progress bar
13
- ThemeEnvy.progress.increment('locales')
14
- } catch (err) {
15
- console.error(err)
16
- }
13
+ locales.forEach(locale => {
14
+ try {
15
+ fs.copySync(locale, path.resolve(ThemeEnvy.outputPath, 'locales', path.basename(locale)))
16
+ // update progress bar
17
+ ThemeEnvy.progress.increment('locales', 1)
18
+ } catch (err) {
19
+ console.error(err)
20
+ }
21
+ })
@@ -7,38 +7,46 @@ const path = require('path')
7
7
  const fs = require('fs-extra')
8
8
  const { getAll } = require('#Build/functions')
9
9
  const elements = getAll('elements').map(file => {
10
- const name = path.basename === 'index.js' ? path.basename(path.dirname(file)) : path.basename(file, '.js')
10
+ const name = path.basename(file) === 'index.js' ? path.basename(path.dirname(file)) : path.basename(file, '.js')
11
11
  return `'${name}': () => import(/* webpackChunkName: "${name}" */ '${file}')`
12
12
  })
13
13
 
14
14
  const markup = elements.length
15
- ? `const elements = {${elements.join(',\n')}}
15
+ ? `const elements = {
16
+ ${elements.join(',\n ')}}
16
17
  // check all elements for presence in the Document and load if they are there
17
- Object.entries(elements).forEach(elm => {
18
- const domElm = document.querySelector(elm[0])
19
- if (domElm) {
20
- if (domElm.getAttribute('loading') === 'lazy') {
21
- const observer = new IntersectionObserver((entries, observer) => {
22
- entries.forEach(entry => {
23
- if (entry.isIntersecting) {
24
- loadElement(elm)
25
- domElm.setAttribute('loading', 'loaded')
26
- observer.disconnect()
27
- }
28
- })
29
- }, { rootMargin: '0px 0px 500px 0px', threshold: 0 })
30
- observer.observe(domElm)
31
- return
32
- }
33
- loadElement(elm)
34
- }
35
- })
18
+ function dynamicElements() {
19
+ Object.entries(elements).forEach(elm => {
20
+ const domElm = document.querySelector(elm[0])
21
+ if (domElm) {
22
+ if (domElm.getAttribute('loading') === 'lazy') {
23
+ const observer = new IntersectionObserver((entries, observer) => {
24
+ entries.forEach(entry => {
25
+ if (entry.isIntersecting) {
26
+ loadElement(elm)
27
+ domElm.setAttribute('loading', 'loaded')
28
+ observer.disconnect()
29
+ }
30
+ })
31
+ }, { rootMargin: '0px 0px 500px 0px', threshold: 0 })
32
+ observer.observe(domElm)
33
+ return
34
+ }
35
+ loadElement(elm)
36
+ }
37
+ })
38
+ }
39
+ dynamicElements()
36
40
  function loadElement(elm) {
37
41
  // load the element
38
42
  elm[1]()
39
43
  // remove from Object so we don't need to check again
40
44
  delete elements[elm[0]]
41
45
  }
46
+ // eslint-disable-next-line no-undef
47
+ if (Shopify.designMode) {
48
+ document.addEventListener('shopify:section:load', dynamicElements)
49
+ }
42
50
  `
43
51
  : ''
44
52
  fs.writeFileSync(path.resolve(__dirname, '../elements.js'), markup, 'utf8')
@@ -4,7 +4,11 @@ const getAll = require('#Build/functions/get-all.js')
4
4
 
5
5
  const criticalCSS = getAll('criticalCSS')
6
6
 
7
- const markup = `${ThemeEnvy.tailwind !== false ? "@import './tailwind-base.css'" : ''};
7
+ const markup = `${ThemeEnvy.tailwind !== false
8
+ ? `@import 'tailwindcss/base';
9
+ @import 'tailwindcss/components';
10
+ @import 'tailwindcss/utilities';`
11
+ : ''}
8
12
  ${criticalCSS.map(file => `@import '${file}';`).join('\n')}
9
13
  `
10
14
 
@@ -1,12 +1,14 @@
1
+ const path = require('path')
2
+
3
+ const { EsbuildPlugin } = require('esbuild-loader')
4
+ const { RetryChunkLoadPlugin } = require('./functions/webpack-plugins/retry-chunk-load-plugin')
1
5
  const MiniCssExtractPlugin = require('mini-css-extract-plugin')
2
- const TerserPlugin = require('terser-webpack-plugin')
3
6
  const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts')
4
- const { ESBuildMinifyPlugin } = require('esbuild-loader')
5
- const path = require('path')
7
+ const TerserPlugin = require('terser-webpack-plugin')
6
8
 
7
9
  module.exports = {
8
10
  entry: {
9
- 'theme-envy': path.resolve(__dirname, 'requires/scripts/theme-envy.js'),
11
+ 'theme-envy': [path.resolve(__dirname, 'requires/scripts/theme-envy.js')],
10
12
  },
11
13
  output: {
12
14
  path: path.resolve(ThemeEnvy.outputPath, 'assets'),
@@ -25,6 +27,7 @@ module.exports = {
25
27
  return '[name].js?h=[contenthash:5]'
26
28
  }
27
29
  },
30
+ devtool: false,
28
31
  module: {
29
32
  rules: [
30
33
  {
@@ -44,7 +47,7 @@ module.exports = {
44
47
  sideEffects: false,
45
48
  minimizer: [
46
49
  (process.env.mode === 'production')
47
- ? new ESBuildMinifyPlugin({
50
+ ? new EsbuildPlugin({
48
51
  target: 'es2015', // Syntax to compile to (see options below for possible values)
49
52
  })
50
53
  : new TerserPlugin({
@@ -56,16 +59,24 @@ module.exports = {
56
59
  alias: {
57
60
  Build: ThemeEnvy.paths.build,
58
61
  Helpers: ThemeEnvy.paths.helpers,
59
- Elements: path.resolve(ThemeEnvy.themePath, 'theme-envy/elements/'),
60
- Features: path.resolve(ThemeEnvy.themePath, 'theme-envy/features/'),
61
62
  Root: path.resolve(process.cwd()),
62
63
  Scripts: path.resolve(ThemeEnvy.themePath, 'scripts/'),
63
64
  },
65
+ modules: ['node_modules'],
66
+ },
67
+ resolveLoader: {
68
+ modules: ['node_modules'],
64
69
  },
65
70
  plugins: [
66
71
  new RemoveEmptyScriptsPlugin(),
67
72
  new MiniCssExtractPlugin({
68
73
  filename: '[name].css?h=[chunkhash:5]',
69
74
  }),
75
+ new RetryChunkLoadPlugin({
76
+ // optional value to set the amount of time in milliseconds before trying to load the chunk again. Default is 0
77
+ retryDelay: 0,
78
+ // optional value to set the maximum number of retries to load the chunk. Default is 1
79
+ maxRetries: 3
80
+ }),
70
81
  ]
71
82
  }
@@ -0,0 +1,20 @@
1
+ const strings = {
2
+ schemaStart: '{% schema %}',
3
+ schemaEnd: '{% endschema %}',
4
+ }
5
+
6
+ module.exports = function(source, file) {
7
+ const schemaStart = source.indexOf(strings.schemaStart)
8
+ const schemaEnd = source.indexOf(strings.schemaEnd)
9
+ if (schemaStart === -1 || schemaEnd === -1) return source
10
+ try {
11
+ return {
12
+ schema: JSON.parse(source.slice(schemaStart + strings.schemaStart.length, schemaEnd)),
13
+ schemaStart,
14
+ schemaEnd
15
+ }
16
+ } catch (error) {
17
+ console.error(file, error)
18
+ process.exit()
19
+ }
20
+ }
@@ -12,7 +12,7 @@ const { spawn } = require('child_process')
12
12
 
13
13
  module.exports = function() {
14
14
  const relativeDistPath = path.relative(process.cwd(), ThemeEnvy.outputPath)
15
- const themePull = ['theme', 'pull', `--store=${ThemeEnvy.store}`, `--path=${relativeDistPath}`, '--only=templates/*.json,config/settings_data.json,sections/*.json']
15
+ const themePull = ['theme', 'pull', `--store=${ThemeEnvy.store}`, `--path=${relativeDistPath}`]
16
16
  const shopify = spawn('shopify', themePull, { cwd: ThemeEnvy.outputPath, stdio: 'inherit' })
17
17
 
18
18
  shopify.on('exit', function() {
package/helpers/index.js CHANGED
@@ -5,6 +5,7 @@ module.exports = {
5
5
  globalThemeEnvy: require('./functions/global-theme-envy.js'),
6
6
  liquidPrettify: require('./functions/liquid-prettify.js'),
7
7
  liquidTree: require('./functions/liquid-tree'),
8
+ parseSchema: require('./functions/parse-schema.js'),
8
9
  pullJson: require('./functions/pull-json.js'),
9
10
  scaffoldNew: require('./functions/scaffold-new'),
10
11
  }
package/index.js CHANGED
@@ -116,6 +116,7 @@ program
116
116
  .description('Build Shopify theme')
117
117
  .usage('[development|production] -w|--watch')
118
118
  .addArgument(new commander.Argument('[env]', 'Specify the build environment to run').choices(['development', 'production']))
119
+ .option('-c, --clean', 'Clean output directory before building')
119
120
  .option('-w, --watch', 'Watch for changed files and update dist, serve with Shopify CLI')
120
121
  .option('-v, --verbose', 'Show Tailwind and Webpack in output')
121
122
  .action((env, options, command) => {
@@ -1,9 +1,9 @@
1
1
  module.exports = {
2
- plugins: [
3
- require('postcss-import'),
4
- require('tailwindcss/nesting'),
5
- require('tailwindcss'),
6
- require('autoprefixer'),
7
- require('cssnano')
8
- ]
2
+ plugins: {
3
+ 'postcss-import': {},
4
+ 'tailwindcss/nesting': {},
5
+ tailwindcss: {},
6
+ autoprefixer: {},
7
+ cssnano: {}
8
+ }
9
9
  }
@@ -2,15 +2,29 @@
2
2
  * @private
3
3
  * @type {import('tailwindcss').Config}
4
4
  */
5
- const theme = require('./theme.config.js')
6
5
  const path = require('path')
7
6
  const ThemeEnvy = require('./theme.config.js')
8
7
 
9
- module.exports = {
10
- content: [path.resolve(ThemeEnvy.themePath, '**/*.{liquid,js}')],
8
+ const config = {
9
+ content: [
10
+ path.resolve(ThemeEnvy.themePath, '**/*.{liquid,js}'),
11
+ ],
11
12
  theme: {
12
13
  extend: {},
13
14
  },
14
- screens: theme.breakpoints,
15
+ screens: ThemeEnvy.breakpoints,
15
16
  plugins: [],
16
17
  }
18
+
19
+ // merge parentTheme tailwind config if it exists
20
+ if (ThemeEnvy.parentTheme?.path) {
21
+ const parentTailwind = require(path.join(path.dirname(require.resolve(ThemeEnvy.parentTheme.path)), 'tailwind.config.js'))
22
+ // deep merge parentTheme tailwind config with child theme tailwind config
23
+ if (parentTailwind) {
24
+ config.theme.extend = Object.assign(config.theme.extend, parentTailwind.theme.extend)
25
+ config.plugins = config.plugins.concat(parentTailwind.plugins)
26
+ config.content = config.content.concat(parentTailwind.content)
27
+ }
28
+ }
29
+
30
+ module.exports = config
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  entry: {
3
- // main: './src/scripts/main.js',
3
+ // main: ['./src/scripts/main.js'],
4
4
  },
5
5
  store: 'my-store.myshopify.com',
6
6
  themePath: 'src',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softlimit/theme-envy",
3
- "version": "0.1.3-alpha",
3
+ "version": "0.1.4-alpha",
4
4
  "description": "Softlimit Shopify Theme Development Environment",
5
5
  "bin": {
6
6
  "theme-envy": "./index.js"
@@ -28,7 +28,6 @@
28
28
  "#Init/functions": "./init/functions/index.js",
29
29
  "#Init/functions/*": "./init/functions/*",
30
30
  "#LogSymbols": "./helpers/functions/log-symbols.js",
31
- "#ParentTheme": "./parent-theme/index.js",
32
31
  "#Root/*": "./*"
33
32
  },
34
33
  "repository": {
@@ -52,7 +51,7 @@
52
51
  "dependencies": {
53
52
  "@shopify/prettier-plugin-liquid": "^1.0.6",
54
53
  "ansi-colors": "^4.1.3",
55
- "autoprefixer": "^10.4.13",
54
+ "autoprefixer": "^10.4.14",
56
55
  "chalk": "^4.1.2",
57
56
  "chokidar": "^3.5.3",
58
57
  "cli-progress": "^3.12.0",
@@ -61,7 +60,7 @@
61
60
  "cssnano": "^5.1.15",
62
61
  "esbuild-loader": "^3.0.1",
63
62
  "fs-extra": "^11.1.0",
64
- "glob": "^8.1.0",
63
+ "glob": "^10.2.1",
65
64
  "mini-css-extract-plugin": "^2.7.2",
66
65
  "node-emoji": "^1.11.0",
67
66
  "path": "^0.12.7",
@@ -71,13 +70,14 @@
71
70
  "promptly": "^3.2.0",
72
71
  "simple-git": "^3.17.0",
73
72
  "stmux": "^1.8.5",
74
- "tailwindcss": "^3.2.7",
73
+ "tailwindcss": "^3.3.1",
75
74
  "terser-webpack-plugin": "^5.3.6",
76
75
  "webpack": "^5.77.0",
77
76
  "webpack-cli": "^5.0.1",
78
77
  "webpack-remove-empty-scripts": "^1.0.1"
79
78
  },
80
79
  "devDependencies": {
80
+ "caller": "^1.1.0",
81
81
  "eslint": "^8.34.0",
82
82
  "eslint-config-standard": "^17.0.0",
83
83
  "eslint-plugin-import": "^2.27.5",
@@ -1,25 +0,0 @@
1
- /**
2
- * Get all files from the parent theme that are not in the child
3
- * @private
4
- * @param {function} func - function to get files with a glob, should return an array of file paths
5
- * @param {array} childFiles - array of file paths from the child theme that we check against
6
- * @returns {array} - array of file paths from the parent theme that are not in the child
7
- */
8
- const path = require('path')
9
- const { directories } = require('#EnsureDirectories')
10
-
11
- module.exports = (func, childFiles, type) => {
12
- const childRelative = childFiles.map(file => path.relative(ThemeEnvy.themePath, file))
13
- // get all files from the parent theme, using only directories listed in ThemeConfig
14
- const only = [...ThemeEnvy.parentTheme.elements, ...ThemeEnvy.parentTheme.features]
15
- if (type === 'schema') {
16
- only.push('schema')
17
- }
18
- if (type === 'liquid') {
19
- only.push(...directories.map(dir => path.resolve(ThemeEnvy.parentTheme, dir)))
20
- }
21
- if (type === 'sectionGroups') {
22
- only.push(path.resolve(ThemeEnvy.parentTheme, 'sections'))
23
- }
24
- return func(ThemeEnvy.parentTheme, only).filter(file => !childRelative.includes(path.relative(ThemeEnvy.parentTheme, file)))
25
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * @private
3
- * @file Runs Tailwind using the Tailwind CLI during build
4
- * @see https://tailwindcss.com/docs/installation#using-tailwind-cli
5
- */
6
- const { spawn } = require('child_process')
7
- const path = require('path')
8
-
9
- module.exports = function({ mode, opts }) {
10
- return new Promise((resolve, reject) => {
11
- const watch = opts.watch || false
12
- const verbose = opts.verbose || false
13
-
14
- // run tailwind
15
- const tailwindCss = path.resolve(__dirname, '../requires/styles/theme-envy.css')
16
- const tailwindOpts = ['tailwindcss', 'build', '-i', tailwindCss, '-o', `${ThemeEnvy.outputPath}/assets/theme-envy.critical.css`]
17
- if (mode === 'production') tailwindOpts.push('--minify')
18
- if (watch) tailwindOpts.push('--watch')
19
- const tailwindOutput = verbose ? { stdio: 'inherit' } : {}
20
- const tailwindProcess = spawn('npx', tailwindOpts, tailwindOutput)
21
- if (watch) {
22
- // watch process does not exit, so we need to increment the progress bar and resolve the promise
23
- ThemeEnvy.progress.increment('tailwind')
24
- resolve()
25
- }
26
- tailwindProcess.on('exit', () => {
27
- ThemeEnvy.progress.increment('tailwind')
28
- resolve()
29
- })
30
- })
31
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * @private
3
- * @file Checks for a parent theme in node_modules or relative path and sets ThemeEnvy.parentTheme to the path
4
- */
5
-
6
- const fs = require('fs-extra')
7
- const path = require('path');
8
-
9
- (() => {
10
- const ThemeConfigPath = path.resolve(process.cwd(), 'theme.config.js')
11
- if (!fs.existsSync(ThemeConfigPath)) return
12
- ThemeEnvy.childPreferred = ThemeEnvy.childPreferred || []
13
-
14
- // look for parent theme in node_modules
15
- const parentThemePath = ThemeEnvy.parentTheme?.path
16
- if (!parentThemePath) return
17
- const parentThemePkg = fs.existsSync(path.resolve(process.cwd(), 'node_modules', parentThemePath))
18
- ? path.resolve(process.cwd(), 'node_modules', parentThemePath)
19
- : false
20
- // look for parent theme in relative path
21
- const parentThemeRelative = fs.existsSync(path.resolve(process.cwd(), parentThemePath))
22
- ? path.resolve(process.cwd(), parentThemePath)
23
- : false
24
- // prefer node_modules over relative path
25
- const parentTheme = parentThemePkg || parentThemeRelative
26
-
27
- ThemeEnvy.parentTheme = parentTheme
28
- })()
@@ -1,3 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;