From 0aea4bac41104ebba944855c7e17068a097767cd Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:31:28 +0200 Subject: [PATCH 01/35] Finish phase 1, html-hmr --- liquidDevEntry.js | 55 ++++++++++++++++++++++++++++++ package.json | 1 + src/components/layout/theme.liquid | 2 ++ webpack.config.js | 34 ++++++++++++------ 4 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 liquidDevEntry.js diff --git a/liquidDevEntry.js b/liquidDevEntry.js new file mode 100644 index 0000000..92f887a --- /dev/null +++ b/liquidDevEntry.js @@ -0,0 +1,55 @@ +const context = require.context('./src', true, /\.liquid$/); + +const cache = {}; + +context.keys().forEach(function (key) { + cache[key] = context(key); +}); + +function replaceHtml(key, startCommentNode) { + const commentNodeType = startCommentNode.nodeType; + while ( + startCommentNode.nextSibling.nodeType !== commentNodeType || + !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) + ) { + startCommentNode.nextSibling.remove(); + } + + const tpl = document.createElement('template'); + tpl.innerHTML = cache[key]; + startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); +} + +if (module.hot) { + module.hot.accept(context.id, function () { + const newContext = require.context('./src', true, /\.liquid$/); + const changes = []; + newContext.keys().forEach(function (key) { + const newFile = newContext(key); + if (cache[key] !== newFile) { + changes.push(key); + cache[key] = newFile; + } + }); + + changes.forEach((changedFile) => { + traverseHMRComments(changedFile, replaceHtml); + }); + }); +} + +function traverseHMRComments(file, callback) { + const nodeIterator = document.createNodeIterator( + document.body, + NodeFilter.SHOW_COMMENT, + function (node) { + return node.nodeValue.includes(`hmr-start: ${file}`) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + } + ); + + while (nodeIterator.nextNode()) { + callback(file, nodeIterator.referenceNode); + } +} diff --git a/package.json b/package.json index 12d074e..2e1c02a 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "postcss-loader": "^4.0.4", "prettier": "^2.1.2", "sass-loader": "^10.1.0", + "string-loader": "^0.0.1", "style-loader": "^2.0.0", "transform-class-properties": "^1.0.0-beta", "webpack": "^5.4.0", diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index ba8cd0b..43a254b 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -30,6 +30,8 @@ {{ content_for_header }} {{ 'tailwind.css' | asset_url | stylesheet_tag }} + {{ 'bundle.runtime.js' | asset_url | script_tag }} + {{ 'bundle.liquidDevEntry.js' | asset_url | script_tag }} {{ 'bundle.theme.css' | asset_url | stylesheet_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index b107eb0..ec3e7e5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,21 +13,31 @@ const publicPath = isDevMode ? `https://localhost:${port}/` : ''; module.exports = { stats: stats, - entry: glob.sync('./src/components/**/*.js').reduce((acc, path) => { - const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); - acc[entry] = path; - return acc; - }, {}), + entry: glob.sync('./src/components/**/*.js').reduce( + (acc, path) => { + const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); + acc[entry] = path; + return acc; + }, + { liquidDevEntry: './liquidDevEntry.js' } + ), output: { - filename: './assets/bundle.[name].js', - hotUpdateChunkFilename: './hot/[id].[fullhash].hot-update.js', - hotUpdateMainFilename: './hot/[fullhash].hot-update.json', + filename: 'assets/bundle.[name].js', + hotUpdateChunkFilename: 'hot/[id].[fullhash].hot-update.js', + hotUpdateMainFilename: 'hot/[fullhash].hot-update.json', path: path.resolve(__dirname, 'dist'), publicPath, }, cache: false, + optimization: { + runtimeChunk: { name: 'runtime' }, + }, module: { rules: [ + { + test: /\.liquid$/, + use: ['string-loader'], + }, { test: /\.(sc|sa|c)ss$/, use: [ @@ -110,7 +120,10 @@ module.exports = { return path.join(targetFolder, path.basename(absolutePath)); }, transform: isDevMode - ? function (content) { + ? function (content, absolutePath) { + const relativePath = path.join(__dirname, 'src'); + const diff = path.relative(relativePath, absolutePath); + content = content .toString() .replace( @@ -127,7 +140,7 @@ module.exports = { } ); - return content; + return `${content}`; } : undefined, }, @@ -160,7 +173,6 @@ module.exports = { https: true, disableHostCheck: true, hot: true, - liveReload: false, overlay: true, writeToDisk: true, }, From 2ad0fb438735d2af05d3dadbac47bae0ed8e706c Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:31:28 +0200 Subject: [PATCH 02/35] Finish phase 1, html-hmr --- liquidDevEntry.js | 55 ++++++++++++++++++++++++++++++ package.json | 1 + src/components/layout/theme.liquid | 4 ++- webpack.config.js | 34 ++++++++++++------ 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 liquidDevEntry.js diff --git a/liquidDevEntry.js b/liquidDevEntry.js new file mode 100644 index 0000000..92f887a --- /dev/null +++ b/liquidDevEntry.js @@ -0,0 +1,55 @@ +const context = require.context('./src', true, /\.liquid$/); + +const cache = {}; + +context.keys().forEach(function (key) { + cache[key] = context(key); +}); + +function replaceHtml(key, startCommentNode) { + const commentNodeType = startCommentNode.nodeType; + while ( + startCommentNode.nextSibling.nodeType !== commentNodeType || + !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) + ) { + startCommentNode.nextSibling.remove(); + } + + const tpl = document.createElement('template'); + tpl.innerHTML = cache[key]; + startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); +} + +if (module.hot) { + module.hot.accept(context.id, function () { + const newContext = require.context('./src', true, /\.liquid$/); + const changes = []; + newContext.keys().forEach(function (key) { + const newFile = newContext(key); + if (cache[key] !== newFile) { + changes.push(key); + cache[key] = newFile; + } + }); + + changes.forEach((changedFile) => { + traverseHMRComments(changedFile, replaceHtml); + }); + }); +} + +function traverseHMRComments(file, callback) { + const nodeIterator = document.createNodeIterator( + document.body, + NodeFilter.SHOW_COMMENT, + function (node) { + return node.nodeValue.includes(`hmr-start: ${file}`) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + } + ); + + while (nodeIterator.nextNode()) { + callback(file, nodeIterator.referenceNode); + } +} diff --git a/package.json b/package.json index fc1cc9b..0fbc0cd 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "postcss-loader": "^4.0.4", "prettier": "^2.1.2", "sass-loader": "^10.1.0", + "string-loader": "^0.0.1", "style-loader": "^2.0.0", "transform-class-properties": "^1.0.0-beta", "webpack": "^5.4.0", diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index 50e7398..43a254b 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -29,7 +29,9 @@ {{ seo_title | strip }} {{ content_for_header }} - {{ 'tailwind.min.css' | asset_url | stylesheet_tag }} + {{ 'tailwind.css' | asset_url | stylesheet_tag }} + {{ 'bundle.runtime.js' | asset_url | script_tag }} + {{ 'bundle.liquidDevEntry.js' | asset_url | script_tag }} {{ 'bundle.theme.css' | asset_url | stylesheet_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index 7e0f955..6edfb74 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,21 +14,31 @@ const publicPath = isDevMode ? `https://localhost:${port}/` : ''; module.exports = { stats: stats, - entry: glob.sync('./src/components/**/*.js').reduce((acc, path) => { - const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); - acc[entry] = path; - return acc; - }, {}), + entry: glob.sync('./src/components/**/*.js').reduce( + (acc, path) => { + const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); + acc[entry] = path; + return acc; + }, + { liquidDevEntry: './liquidDevEntry.js' } + ), output: { - filename: './assets/bundle.[name].js', - hotUpdateChunkFilename: './hot/[id].[fullhash].hot-update.js', - hotUpdateMainFilename: './hot/[fullhash].hot-update.json', + filename: 'assets/bundle.[name].js', + hotUpdateChunkFilename: 'hot/[id].[fullhash].hot-update.js', + hotUpdateMainFilename: 'hot/[fullhash].hot-update.json', path: path.resolve(__dirname, 'dist'), publicPath, }, cache: false, + optimization: { + runtimeChunk: { name: 'runtime' }, + }, module: { rules: [ + { + test: /\.liquid$/, + use: ['string-loader'], + }, { test: /\.(sc|sa|c)ss$/, use: [ @@ -112,7 +122,10 @@ module.exports = { return path.join(targetFolder, path.basename(absolutePath)); }, transform: isDevMode - ? function (content) { + ? function (content, absolutePath) { + const relativePath = path.join(__dirname, 'src'); + const diff = path.relative(relativePath, absolutePath); + content = content .toString() .replace( @@ -129,7 +142,7 @@ module.exports = { } ); - return content; + return `${content}`; } : undefined, }, @@ -162,7 +175,6 @@ module.exports = { https: true, disableHostCheck: true, hot: true, - liveReload: false, overlay: true, writeToDisk: true, }, From ce636465e75e98539eba6cf99678c21afdb3e99a Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:26:13 +0200 Subject: [PATCH 03/35] Add basic liquid process in the pipeline --- liquidDevEntry.js => liquidDev.entry.js | 4 +- liquidDev.loader.js | 51 +++++++++++++++++++++++++ package.json | 2 + src/components/layout/theme.liquid | 4 +- webpack.config.js | 14 +++++-- 5 files changed, 67 insertions(+), 8 deletions(-) rename liquidDevEntry.js => liquidDev.entry.js (91%) create mode 100644 liquidDev.loader.js diff --git a/liquidDevEntry.js b/liquidDev.entry.js similarity index 91% rename from liquidDevEntry.js rename to liquidDev.entry.js index 92f887a..5276ca8 100644 --- a/liquidDevEntry.js +++ b/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /\.liquid$/); +const context = require.context('./src', true, /message\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /\.liquid$/); + const newContext = require.context('./src', true, /message\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/liquidDev.loader.js b/liquidDev.loader.js new file mode 100644 index 0000000..6af258e --- /dev/null +++ b/liquidDev.loader.js @@ -0,0 +1,51 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('liquidjs-section-tags'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('stylesheet_tag', function (v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin( + liquidSectionTags({ + root: liquidFiles, + }) +); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/package.json b/package.json index 0fbc0cd..b2f394d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "file-loader": "^6.0.0", "glob": "^7.1.6", "html-webpack-plugin": "^4.3.0", + "liquidjs": "^9.16.1", + "liquidjs-section-tags": "^1.0.0", "mini-css-extract-plugin": "^1.3.1", "node-sass": "^5.0.0", "postcss-loader": "^4.0.4", diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index 43a254b..e13705e 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -29,9 +29,9 @@ {{ seo_title | strip }} {{ content_for_header }} - {{ 'tailwind.css' | asset_url | stylesheet_tag }} + {{ 'tailwind.min.css' | asset_url | stylesheet_tag }} {{ 'bundle.runtime.js' | asset_url | script_tag }} - {{ 'bundle.liquidDevEntry.js' | asset_url | script_tag }} + {{ 'bundle.liquidDev.js' | asset_url | script_tag }} {{ 'bundle.theme.css' | asset_url | stylesheet_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index 6edfb74..d14b0a7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,7 +20,7 @@ module.exports = { acc[entry] = path; return acc; }, - { liquidDevEntry: './liquidDevEntry.js' } + { liquidDev: './liquidDev.entry.js' } ), output: { filename: 'assets/bundle.[name].js', @@ -35,9 +35,15 @@ module.exports = { }, module: { rules: [ - { + isDevMode && { test: /\.liquid$/, - use: ['string-loader'], + use: [ + 'string-loader', + { + loader: path.resolve(__dirname, 'liquidDev.loader.js'), + options: { publicPath }, + }, + ], }, { test: /\.(sc|sa|c)ss$/, @@ -71,7 +77,7 @@ module.exports = { exclude: /node_modules/, loader: 'babel-loader', }, - ], + ].filter(Boolean), }, plugins: [ new CleanWebpackPlugin({ From b17b94fa3dc1e9522abdb02ef9f6882cae82f705 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 00:49:33 +0200 Subject: [PATCH 04/35] Add support for sections & schema tags --- liquidDev.loader.js | 51 ---------------- liquidDevEntry.js | 55 ----------------- package.json | 2 +- .../liquidDev.entry.js | 4 +- shopify-dev-utils/liquidDev.loader.js | 59 +++++++++++++++++++ shopify-dev-utils/section-tags/index.d.ts | 2 + shopify-dev-utils/section-tags/index.js | 16 +++++ .../section-tags/javascript.d.ts | 2 + shopify-dev-utils/section-tags/javascript.js | 24 ++++++++ shopify-dev-utils/section-tags/schema.d.ts | 2 + shopify-dev-utils/section-tags/schema.js | 45 ++++++++++++++ shopify-dev-utils/section-tags/section.d.ts | 2 + shopify-dev-utils/section-tags/section.js | 30 ++++++++++ .../section-tags/stylesheet.d.ts | 2 + shopify-dev-utils/section-tags/stylesheet.js | 44 ++++++++++++++ shopify-dev-utils/transformLiquid.js | 31 ++++++++++ src/components/layout/theme.liquid | 1 - webpack.config.js | 39 ++++-------- 18 files changed, 274 insertions(+), 137 deletions(-) delete mode 100644 liquidDev.loader.js delete mode 100644 liquidDevEntry.js rename liquidDev.entry.js => shopify-dev-utils/liquidDev.entry.js (89%) create mode 100644 shopify-dev-utils/liquidDev.loader.js create mode 100644 shopify-dev-utils/section-tags/index.d.ts create mode 100644 shopify-dev-utils/section-tags/index.js create mode 100644 shopify-dev-utils/section-tags/javascript.d.ts create mode 100644 shopify-dev-utils/section-tags/javascript.js create mode 100644 shopify-dev-utils/section-tags/schema.d.ts create mode 100644 shopify-dev-utils/section-tags/schema.js create mode 100644 shopify-dev-utils/section-tags/section.d.ts create mode 100644 shopify-dev-utils/section-tags/section.js create mode 100644 shopify-dev-utils/section-tags/stylesheet.d.ts create mode 100644 shopify-dev-utils/section-tags/stylesheet.js create mode 100644 shopify-dev-utils/transformLiquid.js diff --git a/liquidDev.loader.js b/liquidDev.loader.js deleted file mode 100644 index 6af258e..0000000 --- a/liquidDev.loader.js +++ /dev/null @@ -1,51 +0,0 @@ -const loaderUtils = require('loader-utils'); -const path = require('path'); -const { Liquid } = require('liquidjs'); -const glob = require('glob'); -const { liquidSectionTags } = require('liquidjs-section-tags'); - -const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), -]; - -const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" -}); - -engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; -}); - -engine.registerFilter('stylesheet_tag', function (v) { - return ``; // in Dev mode we load css from js for HMR -}); - -engine.registerFilter('script_tag', function (v) { - return ``; -}); - -engine.plugin( - liquidSectionTags({ - root: liquidFiles, - }) -); - -module.exports = function (content) { - if (this.cacheable) this.cacheable(); - - engine.options.loaderOptions = loaderUtils.getOptions(this); - const callback = this.async(); - - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); -}; diff --git a/liquidDevEntry.js b/liquidDevEntry.js deleted file mode 100644 index 92f887a..0000000 --- a/liquidDevEntry.js +++ /dev/null @@ -1,55 +0,0 @@ -const context = require.context('./src', true, /\.liquid$/); - -const cache = {}; - -context.keys().forEach(function (key) { - cache[key] = context(key); -}); - -function replaceHtml(key, startCommentNode) { - const commentNodeType = startCommentNode.nodeType; - while ( - startCommentNode.nextSibling.nodeType !== commentNodeType || - !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) - ) { - startCommentNode.nextSibling.remove(); - } - - const tpl = document.createElement('template'); - tpl.innerHTML = cache[key]; - startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); -} - -if (module.hot) { - module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /\.liquid$/); - const changes = []; - newContext.keys().forEach(function (key) { - const newFile = newContext(key); - if (cache[key] !== newFile) { - changes.push(key); - cache[key] = newFile; - } - }); - - changes.forEach((changedFile) => { - traverseHMRComments(changedFile, replaceHtml); - }); - }); -} - -function traverseHMRComments(file, callback) { - const nodeIterator = document.createNodeIterator( - document.body, - NodeFilter.SHOW_COMMENT, - function (node) { - return node.nodeValue.includes(`hmr-start: ${file}`) - ? NodeFilter.FILTER_ACCEPT - : NodeFilter.FILTER_REJECT; - } - ); - - while (nodeIterator.nextNode()) { - callback(file, nodeIterator.referenceNode); - } -} diff --git a/package.json b/package.json index b2f394d..1046dbf 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "deploy": "webpack --mode=production && npx tailwindcss build src/components/tailwind.css -o dist/assets/tailwind.css.liquid && cleancss -o dist/assets/tailwind.min.css.liquid dist/assets/tailwind.css.liquid && shopify-themekit deploy --env=production" }, "dependencies": { + "sass": "^1.29.0", "tailwindcss": "^1.9.6", "validator": "^13.1.1" }, @@ -31,7 +32,6 @@ "glob": "^7.1.6", "html-webpack-plugin": "^4.3.0", "liquidjs": "^9.16.1", - "liquidjs-section-tags": "^1.0.0", "mini-css-extract-plugin": "^1.3.1", "node-sass": "^5.0.0", "postcss-loader": "^4.0.4", diff --git a/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js similarity index 89% rename from liquidDev.entry.js rename to shopify-dev-utils/liquidDev.entry.js index 5276ca8..a5cdd11 100644 --- a/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /message\.liquid$/); +const context = require.context('../src', true, /(header|message)\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /message\.liquid$/); + const newContext = require.context('../src', true, /(header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js new file mode 100644 index 0000000..cf31d5f --- /dev/null +++ b/shopify-dev-utils/liquidDev.loader.js @@ -0,0 +1,59 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('./section-tags/index'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('paginate', function (_v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('stylesheet_tag', function (_v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin(liquidSectionTags()); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const { isSection } = engine.options.loaderOptions; + + // section handled specially + if (typeof isSection === 'function' && isSection(this.context)) { + const sectionName = path.basename(this.resourcePath, '.liquid'); + content = `{% section "${sectionName}" %}`; + } + + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/shopify-dev-utils/section-tags/index.d.ts b/shopify-dev-utils/section-tags/index.d.ts new file mode 100644 index 0000000..49e17b0 --- /dev/null +++ b/shopify-dev-utils/section-tags/index.d.ts @@ -0,0 +1,2 @@ +import { Liquid } from 'liquidjs'; +export declare function liquidSectionTags(): (this: Liquid) => void; diff --git a/shopify-dev-utils/section-tags/index.js b/shopify-dev-utils/section-tags/index.js new file mode 100644 index 0000000..d0f109e --- /dev/null +++ b/shopify-dev-utils/section-tags/index.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.liquidSectionTags = void 0; +const javascript_1 = require("./javascript"); +const schema_1 = require("./schema"); +const section_1 = require("./section"); +const stylesheet_1 = require("./stylesheet"); +function liquidSectionTags() { + return function () { + this.registerTag('section', section_1.Section); + this.registerTag('schema', schema_1.Schema); + this.registerTag('stylesheet', stylesheet_1.StyleSheet); + this.registerTag('javascript', javascript_1.JavaScript); + }; +} +exports.liquidSectionTags = liquidSectionTags; diff --git a/shopify-dev-utils/section-tags/javascript.d.ts b/shopify-dev-utils/section-tags/javascript.d.ts new file mode 100644 index 0000000..40a02a3 --- /dev/null +++ b/shopify-dev-utils/section-tags/javascript.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const JavaScript: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/javascript.js b/shopify-dev-utils/section-tags/javascript.js new file mode 100644 index 0000000..2c89529 --- /dev/null +++ b/shopify-dev-utils/section-tags/javascript.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JavaScript = void 0; +exports.JavaScript = { + parse: function (tagToken, remainTokens) { + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endjavascript') + stream.stop(); + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function () { + const text = this.tokens.map((token) => token.getText()).join(''); + return ``; + } +}; diff --git a/shopify-dev-utils/section-tags/schema.d.ts b/shopify-dev-utils/section-tags/schema.d.ts new file mode 100644 index 0000000..da4f996 --- /dev/null +++ b/shopify-dev-utils/section-tags/schema.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const Schema: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/schema.js b/shopify-dev-utils/section-tags/schema.js new file mode 100644 index 0000000..71744e2 --- /dev/null +++ b/shopify-dev-utils/section-tags/schema.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Schema = void 0; +function generateSettingsObj(settings) { + if (!Array.isArray(settings)) { + return settings; + } + return settings + .filter((entry) => !!entry.id) + .reduce((sectionSettings, entry) => { + sectionSettings[entry.id] = entry.default; + return sectionSettings; + }, {}); +} +exports.Schema = { + parse: function (tagToken, remainTokens) { + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endschema') { + stream.stop(); + } + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function (ctx) { + const json = this.tokens.map((token) => token.getText()).join(''); + const schema = JSON.parse(json); + const scope = ctx.scopes[ctx.scopes.length - 1]; + scope.section = { + settings: generateSettingsObj(schema.settings), + blocks: (schema.blocks || []).map((block) => ({ + ...block, + settings: generateSettingsObj(block.settings) + })) + }; + return ''; + } +}; diff --git a/shopify-dev-utils/section-tags/section.d.ts b/shopify-dev-utils/section-tags/section.d.ts new file mode 100644 index 0000000..7770b29 --- /dev/null +++ b/shopify-dev-utils/section-tags/section.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs'; +export declare const Section: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/section.js b/shopify-dev-utils/section-tags/section.js new file mode 100644 index 0000000..0baca53 --- /dev/null +++ b/shopify-dev-utils/section-tags/section.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Section = void 0; +const quoted = /^'[^']*'|"[^"]*"$/; +exports.Section = { + parse: function (token) { + this.namestr = token.args; + }, + render: function* (ctx, emitter) { + let name; + if (quoted.exec(this.namestr)) { + const template = this.namestr.slice(1, -1); + name = yield this.liquid._parseAndRender(template, ctx.getAll(), ctx.opts); + } + if (!name) + throw new Error('cannot include with empty filename'); + const templates = yield this.liquid._parseFile(name, ctx.opts, ctx.sync); + // Bubble up schema tag for allowing it's data available to the section + templates.sort((tagA) => { + return tagA.token.kind === 4 && + tagA.token.name === 'schema' + ? -1 + : 0; + }); + const scope = {}; + ctx.push(scope); + yield this.liquid.renderer.renderTemplates(templates, ctx, emitter); + ctx.pop(); + } +}; diff --git a/shopify-dev-utils/section-tags/stylesheet.d.ts b/shopify-dev-utils/section-tags/stylesheet.d.ts new file mode 100644 index 0000000..c09b425 --- /dev/null +++ b/shopify-dev-utils/section-tags/stylesheet.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs'; +export declare const StyleSheet: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/stylesheet.js b/shopify-dev-utils/section-tags/stylesheet.js new file mode 100644 index 0000000..b44809c --- /dev/null +++ b/shopify-dev-utils/section-tags/stylesheet.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StyleSheet = void 0; +const sass_1 = require("sass"); +const quoted = /^'[^']*'|"[^"]*"$/; +const processors = { + '': (x) => x, + sass: sassProcessor, + scss: sassProcessor +}; +exports.StyleSheet = { + parse: function (token, remainTokens) { + this.processor = token.args; + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endstylesheet') + stream.stop(); + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${token.getText()} not closed`); + }); + stream.start(); + }, + render: async function (ctx) { + let processor = ''; + if (quoted.exec(this.processor)) { + const template = this.processor.slice(1, -1); + processor = await this.liquid.parseAndRender(template, ctx.getAll(), ctx.opts); + } + const text = this.tokens.map((token) => token.getText()).join(''); + const p = processors[processor]; + if (!p) + throw new Error(`processor for ${processor} not found`); + const css = await p(text); + return ``; + } +}; +function sassProcessor(data) { + return new Promise((resolve, reject) => sass_1.render({ data }, (err, result) => err ? reject(err) : resolve('' + result.css))); +} diff --git a/shopify-dev-utils/transformLiquid.js b/shopify-dev-utils/transformLiquid.js new file mode 100644 index 0000000..23d80ed --- /dev/null +++ b/shopify-dev-utils/transformLiquid.js @@ -0,0 +1,31 @@ +const path = require('path'); + +module.exports.transformLiquid = function transformLiquid(publicPath) { + return (content, absolutePath) => { + const relativePath = path.join(__dirname, '../src'); + const diff = path.relative(relativePath, absolutePath); + + content = content + .toString() + .replace( + /{{\s*'([^']+)'\s*\|\s*asset_url\s*\|\s*(stylesheet_tag|script_tag)\s*}}/g, + function (matched, fileName, type) { + if (type === 'stylesheet_tag') { + if (fileName !== 'tailwind.min.css') { + return ''; + } + return matched; + } + + return ``; + } + ); + + if(diff.includes('/layout/theme.liquid')) { + // inject HMR entry bundle + content = content.replace('',``) + } + + return `${content}`; + }; +} diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index e13705e..b18931f 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -31,7 +31,6 @@ {{ content_for_header }} {{ 'tailwind.min.css' | asset_url | stylesheet_tag }} {{ 'bundle.runtime.js' | asset_url | script_tag }} - {{ 'bundle.liquidDev.js' | asset_url | script_tag }} {{ 'bundle.theme.css' | asset_url | stylesheet_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index d14b0a7..33672ec 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const WebpackShellPluginNext = require('webpack-shell-plugin-next'); +const { transformLiquid } = require('./shopify-dev-utils/transformLiquid'); const isDevMode = argv.mode === 'development'; @@ -20,7 +21,7 @@ module.exports = { acc[entry] = path; return acc; }, - { liquidDev: './liquidDev.entry.js' } + { liquidDev: './shopify-dev-utils/liquidDev.entry.js' } ), output: { filename: 'assets/bundle.[name].js', @@ -40,8 +41,15 @@ module.exports = { use: [ 'string-loader', { - loader: path.resolve(__dirname, 'liquidDev.loader.js'), - options: { publicPath }, + loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), + options: { + publicPath, + isSection(liquidPath) { + const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); + const componentType = diff.split(path.sep).shift(); + return componentType === 'sections'; + } + }, }, ], }, @@ -127,30 +135,7 @@ module.exports = { const targetFolder = diff.split(path.sep)[0]; return path.join(targetFolder, path.basename(absolutePath)); }, - transform: isDevMode - ? function (content, absolutePath) { - const relativePath = path.join(__dirname, 'src'); - const diff = path.relative(relativePath, absolutePath); - - content = content - .toString() - .replace( - /{{\s*'([^']+)'\s*\|\s*asset_url\s*\|\s*(stylesheet_tag|script_tag)\s*}}/g, - function (matched, fileName, type) { - if (type === 'stylesheet_tag') { - if (fileName !== 'tailwind.min.css') { - return ''; - } - return matched; - } - - return ``; - } - ); - - return `${content}`; - } - : undefined, + transform: isDevMode ? transformLiquid(publicPath) : undefined, }, { from: 'src/assets/**/*', From 6d0ff6c9ed0cec955c3c3579e357d00f5ef96d03 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 21:23:25 +0200 Subject: [PATCH 05/35] Add static store data --- shopify-dev-utils/liquidDev.entry.js | 4 ++-- shopify-dev-utils/liquidDev.loader.js | 6 ++++-- shopify-dev-utils/storeData.js | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 shopify-dev-utils/storeData.js diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index a5cdd11..4eee04f 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('../src', true, /(header|message)\.liquid$/); +const context = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('../src', true, /(header|message)\.liquid$/); + const newContext = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index cf31d5f..46f8b84 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -2,6 +2,7 @@ const loaderUtils = require('loader-utils'); const path = require('path'); const { Liquid } = require('liquidjs'); const glob = require('glob'); +const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const liquidFiles = [ @@ -16,7 +17,8 @@ const liquidFiles = [ const engine = new Liquid({ root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" + extname: '.liquid', // used for layouts/includes, defaults "", + globals: fetchStoreData() }); engine.registerFilter('asset_url', function (v) { @@ -26,7 +28,7 @@ engine.registerFilter('asset_url', function (v) { }); engine.registerFilter('paginate', function (_v) { - return ``; // in Dev mode we load css from js for HMR + return ``; }); engine.registerFilter('stylesheet_tag', function (_v) { diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js new file mode 100644 index 0000000..83af7e2 --- /dev/null +++ b/shopify-dev-utils/storeData.js @@ -0,0 +1,26 @@ +module.exports.fetchStoreData = function fetchStoreData() { + + return { + 'shop': { + 'name': 'themekit-webpack-test' + }, + 'linklists': { + 'main-menu': { + 'title': '', + 'levels': 1, + 'links': [ + { + 'title': 'Home', + 'url': '/', + 'links': [] + }, + { + 'title': 'Catalog', + 'url': '/collections/all', + 'links': [] + } + ] + } + } + }; +}; From dc4b5ae6fbe328c457bb3beca7e5b5bb134bf327 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Sat, 28 Nov 2020 16:41:35 +0200 Subject: [PATCH 06/35] Add support for paginate tag --- shopify-dev-utils/liquidDev.entry.js | 12 +++- shopify-dev-utils/liquidDev.loader.js | 58 ++++++++--------- shopify-dev-utils/tags/paginate.d.ts | 2 + shopify-dev-utils/tags/paginate.js | 93 +++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 shopify-dev-utils/tags/paginate.d.ts create mode 100644 shopify-dev-utils/tags/paginate.js diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index 4eee04f..bbbf9ab 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,8 @@ -const context = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); +const context = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ +); const cache = {}; @@ -22,7 +26,11 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); + const newContext = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ + ); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 46f8b84..bfaed8b 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -4,58 +4,56 @@ const { Liquid } = require('liquidjs'); const glob = require('glob'); const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); +const { Paginate } = require('./tags/paginate'); const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), ]; const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "", - globals: fetchStoreData() + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "", + globals: fetchStoreData(), }); engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; + const { publicPath } = this.context.opts.loaderOptions; - return `${publicPath}${v}`; -}); - -engine.registerFilter('paginate', function (_v) { - return ``; + return `${publicPath}${v}`; }); engine.registerFilter('stylesheet_tag', function (_v) { - return ``; // in Dev mode we load css from js for HMR + return ``; // in Dev mode we load css from js for HMR }); engine.registerFilter('script_tag', function (v) { - return ``; + return ``; }); +engine.registerTag('paginate', Paginate); engine.plugin(liquidSectionTags()); module.exports = function (content) { - if (this.cacheable) this.cacheable(); + if (this.cacheable) this.cacheable(); - engine.options.loaderOptions = loaderUtils.getOptions(this); - const { isSection } = engine.options.loaderOptions; + engine.options.loaderOptions = loaderUtils.getOptions(this); + const { isSection } = engine.options.loaderOptions; - // section handled specially - if (typeof isSection === 'function' && isSection(this.context)) { - const sectionName = path.basename(this.resourcePath, '.liquid'); - content = `{% section "${sectionName}" %}`; - } + // section handled specially + if (typeof isSection === 'function' && isSection(this.context)) { + const sectionName = path.basename(this.resourcePath, '.liquid'); + content = `{% section "${sectionName}" %}`; + } - const callback = this.async(); + const callback = this.async(); - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); }; diff --git a/shopify-dev-utils/tags/paginate.d.ts b/shopify-dev-utils/tags/paginate.d.ts new file mode 100644 index 0000000..34abbfe --- /dev/null +++ b/shopify-dev-utils/tags/paginate.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const Paginate: TagImplOptions; diff --git a/shopify-dev-utils/tags/paginate.js b/shopify-dev-utils/tags/paginate.js new file mode 100644 index 0000000..f43bcb2 --- /dev/null +++ b/shopify-dev-utils/tags/paginate.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Paginate = void 0; +const liquidjs_1 = require("liquidjs"); +function generatePaginateObj({ offset, perPage, total }) { + const pages = Math.ceil(total / perPage); + const currentPage = Math.floor((offset + perPage) / perPage); + const paginate = { + current_offset: offset, + current_page: currentPage, + items: total, + page_size: perPage, + parts: Array(pages) + .fill(0) + .map((_, index) => { + const page = index + 1; + if (page === currentPage) { + return { title: page, is_link: false }; + } + return { title: page, url: `?page=${page}`, is_link: true }; + }), + pages, + previous: undefined, + next: undefined + }; + if (currentPage === pages && pages > 1) { + paginate.previous = { + title: '\u0026laquo; Previous', + url: `?page=${currentPage - 1}`, + is_link: true + }; + } + else if (currentPage < pages && pages > 1) { + paginate.next = { + title: 'Next \u0026raquo;', + url: `?page=${currentPage + 1}`, + is_link: true + }; + } + return paginate; +} +function populateVariableObj({ data, depth }) { + return depth.reverse().reduce((result, prop) => { + return { [prop.getText()]: result }; + }, data); +} +exports.Paginate = { + parse: function (tagToken, remainTokens) { + this.templates = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('start', () => { + const toknenizer = new liquidjs_1.Tokenizer(tagToken.args); + const list = toknenizer.readValue(); + const by = toknenizer.readWord(); + const perPage = toknenizer.readValue(); + liquidjs_1.assert(list.size() && + by.content === 'by' && + +perPage.getText() > 0 && + +perPage.getText() <= 50, () => `illegal tag: ${tagToken.getText()}`); + this.args = { list, perPage: +perPage.getText() }; + }) + .on('tag:endpaginate', () => stream.stop()) + .on('template', (tpl) => { + this.templates.push(tpl); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function* (ctx, emitter) { + const list = yield liquidjs_1.evalToken(this.args.list, ctx) || []; + const perPage = this.args.perPage; + const currentPage = +ctx.get(['current_page']); + const offset = currentPage ? (currentPage - 1) * perPage : 0; + const variableName = this.args.list.getVariableAsText(); + const scopeList = list.slice(offset, offset + perPage); + const data = populateVariableObj({ + data: scopeList, + depth: this.args.list.props + }); + const paginate = generatePaginateObj({ + offset, + perPage, + total: list.length + }); + const scope = { [variableName]: data, paginate }; + ctx.push(scope); + yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter); + ctx.pop(); + } +}; From a4b186453ba9953defd427b6064687b807b31399 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 25 Nov 2020 16:35:27 -0800 Subject: [PATCH 07/35] collection-list updated --- .../templates/collection.list.liquid | 17 --------- .../templates/list-collections.liquid | 35 +++++++++++-------- 2 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 src/components/templates/collection.list.liquid diff --git a/src/components/templates/collection.list.liquid b/src/components/templates/collection.list.liquid deleted file mode 100644 index 7161b43..0000000 --- a/src/components/templates/collection.list.liquid +++ /dev/null @@ -1,17 +0,0 @@ -{% paginate collection.products by 2 %} -

{{ collection.title }}

- {% for product in collection.products %} -
- {{ product.title }} - {{ product.price | money }} - {% unless product.available %}
sold out{% endunless %} - - {{ product.featured_image.alt | escape }} - -
- {% else %} -

no matches

- {% endfor %} - {% if paginate.pages > 1 %}{{ paginate | default_pagination }}{% endif %} -{% endpaginate %} - diff --git a/src/components/templates/list-collections.liquid b/src/components/templates/list-collections.liquid index 5e36edd..99b9adc 100644 --- a/src/components/templates/list-collections.liquid +++ b/src/components/templates/list-collections.liquid @@ -1,17 +1,22 @@ {% for collection in collections %} - {% unless collection.handle == 'frontpage' %} - {% capture collection_title %}{{ collection.title | escape }}{% endcapture %} - More {{ collection_title }} › - {% for product in collection.products limit:5 %} - {% assign grid_item_width = 'large--one-fifth medium--one-half' %} -
- {{ product.title }} - {{ product.price | money }} - {% unless product.available %}
sold out{% endunless %} - - {{ product.featured_image.alt | escape }} - +{% unless collection.handle == 'frontpage' %} +{% capture collection_title %}{{ collection.title | escape }}{% endcapture %} +More {{ collection_title }} › + +{% endunless %} +{% endfor %} \ No newline at end of file From 0c90c673681f9830ebcf325fe443641efafc5c13 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 27 Nov 2020 19:52:08 -0800 Subject: [PATCH 08/35] collections-list setup pagination --- .../pagination/accessible-pagination.liquid | 33 ++++++++++ .../pagination/default-pagination.liquid | 3 + .../templates/list-collections.liquid | 62 ++++++++++++------- src/config/settings_schema.json | 44 +++++++++++-- 4 files changed, 116 insertions(+), 26 deletions(-) create mode 100644 src/components/snippets/pagination/accessible-pagination.liquid create mode 100644 src/components/snippets/pagination/default-pagination.liquid diff --git a/src/components/snippets/pagination/accessible-pagination.liquid b/src/components/snippets/pagination/accessible-pagination.liquid new file mode 100644 index 0000000..d3389a2 --- /dev/null +++ b/src/components/snippets/pagination/accessible-pagination.liquid @@ -0,0 +1,33 @@ +{%- if paginate.pages > 1 -%} + +{%- endif -%} diff --git a/src/components/snippets/pagination/default-pagination.liquid b/src/components/snippets/pagination/default-pagination.liquid new file mode 100644 index 0000000..f8ecad6 --- /dev/null +++ b/src/components/snippets/pagination/default-pagination.liquid @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/components/templates/list-collections.liquid b/src/components/templates/list-collections.liquid index 99b9adc..d5c4842 100644 --- a/src/components/templates/list-collections.liquid +++ b/src/components/templates/list-collections.liquid @@ -1,22 +1,42 @@ -{% for collection in collections %} -{% unless collection.handle == 'frontpage' %} -{% capture collection_title %}{{ collection.title | escape }}{% endcapture %} -More {{ collection_title }} › -
- {% for product in collection.products limit:5 %} - - + {%- endunless -%} + {%- endfor -%} + + {%- if settings.pagination -%} + {%- comment -%}Pagination Control in Theme Settings{%- endcomment -%} + {%- if settings.pagination_type == 'accessable_pagination' -%} + {% include 'accessible-pagination' %} + {%- endif -%} + {%- if settings.pagination_type == 'default_pagination' -%} + {% include 'default-pagination' %} + {%- endif -%} + {%- if settings.pagination_type == 'infinite_load' -%} + {% include 'infinite_load' %} + {%- endif -%} + {%- if settings.pagination_type == 'infinite_scroll' -%} + {% include 'infinite_scroll' %} + {%- endif -%} + {%- endif -%} + {%- endpaginate -%} + \ No newline at end of file diff --git a/src/config/settings_schema.json b/src/config/settings_schema.json index 2222269..870345b 100644 --- a/src/config/settings_schema.json +++ b/src/config/settings_schema.json @@ -1,10 +1,44 @@ -[ - { +[{ "name": "theme_info", - "theme_name": "Themekit template theme", + "theme_name": "Themekit Webpack", "theme_version": "1.0.0", - "theme_author": "Shopify", + "theme_author": "Mike Salvati", "theme_documentation_url": "https://github.com/Shopify/themekit", "theme_support_url": "https://github.com/Shopify/themekit/issues" + }, + { + "name": "Pagination Settings", + "settings": [{ + "type": "checkbox", + "id": "pagination", + "label": "Enable Pagination", + "default": true, + "info": "Toggle ON/OFF Pagination" + }, + { + "type": "radio", + "id": "pagination_type", + "label": "Pagination Type", + "options": [{ + "value": "accessable_pagination", + "label": "Accessible Pagination" + }, + { + "value": "default_pagination", + "label": "Default Pagination" + }, + { + "value": "infinite_load", + "label": "Infinite Load" + }, + { + "value": "infinite_scroll", + "label": "Infinite Scroll" + } + ], + "default": "accessable_pagination", + "info": "Select Pagination Style" + } + ] } -] +] \ No newline at end of file From 42c68da68cd212b7de434ff2ae5d93c397b5c589 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 27 Nov 2020 20:07:18 -0800 Subject: [PATCH 09/35] removed infiniate scroll for now --- src/components/templates/list-collections.liquid | 6 ------ src/config/settings_schema.json | 8 -------- 2 files changed, 14 deletions(-) diff --git a/src/components/templates/list-collections.liquid b/src/components/templates/list-collections.liquid index d5c4842..8f148d4 100644 --- a/src/components/templates/list-collections.liquid +++ b/src/components/templates/list-collections.liquid @@ -31,12 +31,6 @@ {%- if settings.pagination_type == 'default_pagination' -%} {% include 'default-pagination' %} {%- endif -%} - {%- if settings.pagination_type == 'infinite_load' -%} - {% include 'infinite_load' %} - {%- endif -%} - {%- if settings.pagination_type == 'infinite_scroll' -%} - {% include 'infinite_scroll' %} - {%- endif -%} {%- endif -%} {%- endpaginate -%} \ No newline at end of file diff --git a/src/config/settings_schema.json b/src/config/settings_schema.json index 870345b..a4e9e87 100644 --- a/src/config/settings_schema.json +++ b/src/config/settings_schema.json @@ -26,14 +26,6 @@ { "value": "default_pagination", "label": "Default Pagination" - }, - { - "value": "infinite_load", - "label": "Infinite Load" - }, - { - "value": "infinite_scroll", - "label": "Infinite Scroll" } ], "default": "accessable_pagination", From 46607d3b01b6626ce20f8a63a87fe07b0419b04a Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 11:36:02 -0800 Subject: [PATCH 10/35] product template updated --- src/components/templates/collection.liquid | 50 +++++++---- .../templates/list-collections.liquid | 6 +- src/components/templates/product.liquid | 89 +++++++++++++------ 3 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/components/templates/collection.liquid b/src/components/templates/collection.liquid index dc91d97..3940917 100644 --- a/src/components/templates/collection.liquid +++ b/src/components/templates/collection.liquid @@ -1,18 +1,36 @@ -{% paginate collection.products by 2 %} +
+ {% paginate collection.products by 8 %}

{{ collection.title }}

- {% for product in collection.products %} -
- {{ product.title }} - {{ product.price | money }} - {% unless product.available %}
sold out{% endunless %} - - {{ product.featured_image.alt | escape }} - -
- {% else %} +
+ {% for product in collection.products %} + +
+
+

{{ product.title }}

+

{{ product.content | truncate: 40 }}

+
+ {{ product.featured_image.alt | escape }} +
+

{{ product.price | money_without_trailing_zeros }}

+ +
+
+
+ {% else %}

no matches

- {% endfor %} - {% if paginate.pages > 1 %} - {{ paginate | default_pagination }} - {% endif %} -{% endpaginate %} + {% endfor %} +
+ {%- if settings.pagination and paginate.pages > 1 -%} + {%- comment -%}Pagination Control in Theme Settings{%- endcomment -%} + {%- if settings.pagination_type == 'accessable_pagination' -%} + {% include 'accessible-pagination' %} + {%- endif -%} + {%- if settings.pagination_type == 'default_pagination' -%} + {% include 'default-pagination' %} + {%- endif -%} + {%- endif -%} + {% endpaginate %} +
\ No newline at end of file diff --git a/src/components/templates/list-collections.liquid b/src/components/templates/list-collections.liquid index 8f148d4..23c9d3f 100644 --- a/src/components/templates/list-collections.liquid +++ b/src/components/templates/list-collections.liquid @@ -6,12 +6,12 @@ {{ collection_title }} ›
{%- for product in collection.products limit:4 -%} - +
{{ product.featured_image.alt | escape }} -

{{ product.title }}

+

{{ product.title }}

{%- unless product.available -%} sold out {%- endunless -%} @@ -23,7 +23,7 @@ {%- endunless -%} {%- endfor -%} - {%- if settings.pagination -%} + {%- if settings.pagination and paginate.pages > 1 -%} {%- comment -%}Pagination Control in Theme Settings{%- endcomment -%} {%- if settings.pagination_type == 'accessable_pagination' -%} {% include 'accessible-pagination' %} diff --git a/src/components/templates/product.liquid b/src/components/templates/product.liquid index c600d5d..a450ec9 100644 --- a/src/components/templates/product.liquid +++ b/src/components/templates/product.liquid @@ -1,30 +1,63 @@ {% assign current_variant = product.selected_or_first_available_variant %} {% assign featured_image = current_variant.featured_image | default: product.featured_image %} - -{{ featured_image.alt | escape }} -{% for image in product.images %} -
- {{ image.alt | escape }} - -{% endfor %} -

{{ product.title }}

-
- - {{ current_variant.price | money }} - - - -
-
{{ product.description }}
+
+
+
+ {{ featured_image.alt | escape }} +
+ {%- comment -%}

BRAND NAME

{%- endcomment -%} +

{{ product.title }}

+

{{ product.content }}

+
+
+
+ Color +
+ + + + + + +
+
+
+ Size +
+ + + + + + +
+
+
+
+ {{ product.price | money_without_trailing_zeros }} + +
+
+
+
+
+
\ No newline at end of file From b16c84e4745cde7b405fe3ebacbc95b09c365d36 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 12:39:13 -0800 Subject: [PATCH 11/35] ESLint added to webpack --- .eslintrc | 10 ++++++++++ package.json | 8 ++++++++ src/components/layout/theme.js | 4 ++-- webpack.config.js | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..cd0adff --- /dev/null +++ b/.eslintrc @@ -0,0 +1,10 @@ +{ + "parser": "babel-eslint", + "extends": [ + "plugin:@shopify/esnext", + "plugin:@shopify/node" + ], + "rules": { + "quotes": [2, "single", { "avoidEscape": true }] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 1046dbf..7a713f4 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "devDependencies": { "@babel/core": "^7.11.4", "@babel/preset-env": "^7.11.0", + "@shopify/eslint-plugin": "^39.0.3", "@shopify/themekit": "^1.1.6", "autoprefixer": "^9.8.6", + "babel-eslint": "^10.1.0", "babel-loader": "^8.1.0", "babel-plugin-transform-class-properties": "^6.24.1", "browser-sync": "^2.26.12", @@ -28,6 +30,12 @@ "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.3.0", "css-loader": "^5.0.1", + "eslint": "7.2.0", + "eslint-loader": "^4.0.2", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "4.0.0", "file-loader": "^6.0.0", "glob": "^7.1.6", "html-webpack-plugin": "^4.3.0", diff --git a/src/components/layout/theme.js b/src/components/layout/theme.js index fe0fa0c..8213437 100644 --- a/src/components/layout/theme.js +++ b/src/components/layout/theme.js @@ -1,4 +1,4 @@ -import "./theme.scss"; +import './theme.scss'; const themeFunction = () => 'Theme JS ES6 Function HMR TEST!'; -console.log(themeFunction()) \ No newline at end of file +console.log(themeFunction()); diff --git a/webpack.config.js b/webpack.config.js index 33672ec..335ffdb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -83,7 +83,7 @@ module.exports = { { test: /\.js$/, exclude: /node_modules/, - loader: 'babel-loader', + use: ["babel-loader", "eslint-loader"] }, ].filter(Boolean), }, From 5799b7cd3d0a11bddd2ceec29a6490439f92f55c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 13:26:00 -0800 Subject: [PATCH 12/35] Create CONTRIBUTING.md I figure this is good enough to start --- CONTRIBUTING.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bb5159c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +## Contributing + +First off, thank you for considering contributing to A Shopify Theme with Themekit and Webpack. It's exciting to see what we can do with modern build tools. + +### Where do I go from here? + +If you notice a bug or have a feature request, [make one](https://github.com/activeadmin/activeadmin/issues/new)! I'm no pro, creating issues helps improve this repo and helps learn about all that is involved building themes. + +### Fork & create a branch + +If there is something you think you can fix, then [fork themekit-webpack](https://help.github.com/articles/fork-a-repo) and create +a branch with a descriptive name. + +A good branch name would be (where issue #33 is the ticket you're working on): + +```sh +git checkout -b 33-add-infinite-scroll-feature +``` + +### Verify build with ESLint +When testing your new feature ensure you are passing ESLint rules by running +```sh +yarn build +``` +Make note of any errors that get flagged from ESLint and fix before creating a PR. + +### Make a Pull Request + +At this point, you should switch back to your master branch and make sure it's +up to date with themekit-webpack's master branch: + +```sh +git remote add upstream git@github.com:themekit-webpack/themekit-webpack.git +git checkout master +git pull upstream master +``` + +Then update your feature branch from your local copy of master, and push it! + +```sh +git checkout 33-add-infinite-scroll-feature +git rebase master +git push --set-upstream origin 33-add-infinite-scroll-feature +``` +Finally, go to GitHub and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request) 😎 +### Keeping your Pull Request updated + +If a maintainer asks you to "rebase" your PR, they're saying that a lot of code +has changed, and that you need to update your branch so it's easier to merge. + +To learn more about rebasing in Git, there are a lot of good [git rebasing](http://git-scm.com/book/en/Git-Branching-Rebasing) [resources](https://help.github.com/en/github/using-git/about-git-rebase) but here's the suggested workflow: + +```sh +git checkout 33-add-infinite-scroll-feature +git pull --rebase upstream master +git push --force-with-lease 33-add-infinite-scroll-feature +``` +### Merging a PR (maintainers only) + +A PR can only be merged into `master` by a maintainer if: + +* It is passing ESLint. +* It has no requested changes. +* It is up to date with current master. + +Any maintainer is allowed to merge a PR if all of these conditions are met. From 657b80eadfacf2895b3a869eedf75a3a7614d3df Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 13:32:30 -0800 Subject: [PATCH 13/35] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f5164c1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at themekit@3daddict.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From fa8d3b17b9037fc486e1b13a8791dd59be0cb356 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 13:36:27 -0800 Subject: [PATCH 14/35] added refs to conduct --- CONTRIBUTING.md | 1 + readme.md | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb5159c..baf31b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ First off, thank you for considering contributing to A Shopify Theme with Themek ### Where do I go from here? If you notice a bug or have a feature request, [make one](https://github.com/activeadmin/activeadmin/issues/new)! I'm no pro, creating issues helps improve this repo and helps learn about all that is involved building themes. +Please make sure to check the [CODE_OF_CONDUCT.md](https://github.com/3daddict/themekit-webpack/blob/master/CODE_OF_CONDUCT.md) before contributing to this repo. ### Fork & create a branch diff --git a/readme.md b/readme.md index 13a173d..d62783b 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,11 @@ # Shopify ThemeKit with Webpack 4 This is a starter Theme using Webpack, ThemeKit and TailwindCSS for developing Shopify themes with modern build tools. The goal is to create a tool with a component-based folder structure and is easy to use. +## Contributing +To contribute to this repo please see +- [CONTRIBUTING.md](https://github.com/3daddict/themekit-webpack/blob/master/CONTRIBUTING.md) +- [CODE_OF_CONDUCT.md](https://github.com/3daddict/themekit-webpack/blob/master/CODE_OF_CONDUCT.md) + ## 🎯 Goals - [x] Component based folder structure - [x] ES6 Modules @@ -94,4 +99,4 @@ In the event that you find the HMR assets are not loading and the requests to lo ## 🛣️ Roadmap - [ ] Finalization and First Release - [x] Update copy-webpack-plugin to v6 [Issue #519](https://github.com/webpack-contrib/copy-webpack-plugin/issues/519) Thanks [@felixmosh](https://github.com/felixmosh)! -- [ ] Webpack 5? 🤔 +- [x] Webpack 5? 🤔 Thanks [@felixmosh](https://github.com/felixmosh)! From 8c46638daf7323da480c0e2524240d520699b9e2 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:31:28 +0200 Subject: [PATCH 15/35] Finish phase 1, html-hmr --- liquidDevEntry.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 4 ++++ 2 files changed, 59 insertions(+) create mode 100644 liquidDevEntry.js diff --git a/liquidDevEntry.js b/liquidDevEntry.js new file mode 100644 index 0000000..92f887a --- /dev/null +++ b/liquidDevEntry.js @@ -0,0 +1,55 @@ +const context = require.context('./src', true, /\.liquid$/); + +const cache = {}; + +context.keys().forEach(function (key) { + cache[key] = context(key); +}); + +function replaceHtml(key, startCommentNode) { + const commentNodeType = startCommentNode.nodeType; + while ( + startCommentNode.nextSibling.nodeType !== commentNodeType || + !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) + ) { + startCommentNode.nextSibling.remove(); + } + + const tpl = document.createElement('template'); + tpl.innerHTML = cache[key]; + startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); +} + +if (module.hot) { + module.hot.accept(context.id, function () { + const newContext = require.context('./src', true, /\.liquid$/); + const changes = []; + newContext.keys().forEach(function (key) { + const newFile = newContext(key); + if (cache[key] !== newFile) { + changes.push(key); + cache[key] = newFile; + } + }); + + changes.forEach((changedFile) => { + traverseHMRComments(changedFile, replaceHtml); + }); + }); +} + +function traverseHMRComments(file, callback) { + const nodeIterator = document.createNodeIterator( + document.body, + NodeFilter.SHOW_COMMENT, + function (node) { + return node.nodeValue.includes(`hmr-start: ${file}`) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + } + ); + + while (nodeIterator.nextNode()) { + callback(file, nodeIterator.referenceNode); + } +} diff --git a/webpack.config.js b/webpack.config.js index 335ffdb..0df5606 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,6 +53,10 @@ module.exports = { }, ], }, + { + test: /\.liquid$/, + use: ['string-loader'], + }, { test: /\.(sc|sa|c)ss$/, use: [ From 62f754b1b7d236da1068170355fc591c5c11410a Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:26:13 +0200 Subject: [PATCH 16/35] Add basic liquid process in the pipeline --- liquidDevEntry.js => liquidDev.entry.js | 4 +- liquidDev.loader.js | 51 +++++++++++++++++++++++++ webpack.config.js | 8 +++- 3 files changed, 60 insertions(+), 3 deletions(-) rename liquidDevEntry.js => liquidDev.entry.js (91%) create mode 100644 liquidDev.loader.js diff --git a/liquidDevEntry.js b/liquidDev.entry.js similarity index 91% rename from liquidDevEntry.js rename to liquidDev.entry.js index 92f887a..5276ca8 100644 --- a/liquidDevEntry.js +++ b/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /\.liquid$/); +const context = require.context('./src', true, /message\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /\.liquid$/); + const newContext = require.context('./src', true, /message\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/liquidDev.loader.js b/liquidDev.loader.js new file mode 100644 index 0000000..6af258e --- /dev/null +++ b/liquidDev.loader.js @@ -0,0 +1,51 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('liquidjs-section-tags'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('stylesheet_tag', function (v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin( + liquidSectionTags({ + root: liquidFiles, + }) +); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/webpack.config.js b/webpack.config.js index 0df5606..340ef2e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,7 +55,13 @@ module.exports = { }, { test: /\.liquid$/, - use: ['string-loader'], + use: [ + 'string-loader', + { + loader: path.resolve(__dirname, 'liquidDev.loader.js'), + options: { publicPath }, + }, + ], }, { test: /\.(sc|sa|c)ss$/, From 932e5e309316ec3f01698848bad4d52d2e5044a0 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 00:49:33 +0200 Subject: [PATCH 17/35] Add support for sections & schema tags --- liquidDev.entry.js | 55 ---------------------------- liquidDev.loader.js | 51 -------------------------- shopify-dev-utils/liquidDev.entry.js | 12 +----- webpack.config.js | 11 +++++- 4 files changed, 11 insertions(+), 118 deletions(-) delete mode 100644 liquidDev.entry.js delete mode 100644 liquidDev.loader.js diff --git a/liquidDev.entry.js b/liquidDev.entry.js deleted file mode 100644 index 5276ca8..0000000 --- a/liquidDev.entry.js +++ /dev/null @@ -1,55 +0,0 @@ -const context = require.context('./src', true, /message\.liquid$/); - -const cache = {}; - -context.keys().forEach(function (key) { - cache[key] = context(key); -}); - -function replaceHtml(key, startCommentNode) { - const commentNodeType = startCommentNode.nodeType; - while ( - startCommentNode.nextSibling.nodeType !== commentNodeType || - !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) - ) { - startCommentNode.nextSibling.remove(); - } - - const tpl = document.createElement('template'); - tpl.innerHTML = cache[key]; - startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); -} - -if (module.hot) { - module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /message\.liquid$/); - const changes = []; - newContext.keys().forEach(function (key) { - const newFile = newContext(key); - if (cache[key] !== newFile) { - changes.push(key); - cache[key] = newFile; - } - }); - - changes.forEach((changedFile) => { - traverseHMRComments(changedFile, replaceHtml); - }); - }); -} - -function traverseHMRComments(file, callback) { - const nodeIterator = document.createNodeIterator( - document.body, - NodeFilter.SHOW_COMMENT, - function (node) { - return node.nodeValue.includes(`hmr-start: ${file}`) - ? NodeFilter.FILTER_ACCEPT - : NodeFilter.FILTER_REJECT; - } - ); - - while (nodeIterator.nextNode()) { - callback(file, nodeIterator.referenceNode); - } -} diff --git a/liquidDev.loader.js b/liquidDev.loader.js deleted file mode 100644 index 6af258e..0000000 --- a/liquidDev.loader.js +++ /dev/null @@ -1,51 +0,0 @@ -const loaderUtils = require('loader-utils'); -const path = require('path'); -const { Liquid } = require('liquidjs'); -const glob = require('glob'); -const { liquidSectionTags } = require('liquidjs-section-tags'); - -const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), -]; - -const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" -}); - -engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; -}); - -engine.registerFilter('stylesheet_tag', function (v) { - return ``; // in Dev mode we load css from js for HMR -}); - -engine.registerFilter('script_tag', function (v) { - return ``; -}); - -engine.plugin( - liquidSectionTags({ - root: liquidFiles, - }) -); - -module.exports = function (content) { - if (this.cacheable) this.cacheable(); - - engine.options.loaderOptions = loaderUtils.getOptions(this); - const callback = this.async(); - - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); -}; diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index bbbf9ab..a5cdd11 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,8 +1,4 @@ -const context = require.context( - '../src', - true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ -); +const context = require.context('../src', true, /(header|message)\.liquid$/); const cache = {}; @@ -26,11 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context( - '../src', - true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ - ); + const newContext = require.context('../src', true, /(header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/webpack.config.js b/webpack.config.js index 340ef2e..1d0b5db 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -58,8 +58,15 @@ module.exports = { use: [ 'string-loader', { - loader: path.resolve(__dirname, 'liquidDev.loader.js'), - options: { publicPath }, + loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), + options: { + publicPath, + isSection(liquidPath) { + const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); + const componentType = diff.split(path.sep).shift(); + return componentType === 'sections'; + } + }, }, ], }, From dd4966ae7f4b63b456268b24090b96d9c609af39 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:31:28 +0200 Subject: [PATCH 18/35] Finish phase 1, html-hmr --- liquidDevEntry.js | 55 ++++++++++++++++++++++++++++++ package.json | 1 + src/components/layout/theme.liquid | 4 ++- webpack.config.js | 34 ++++++++++++------ 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 liquidDevEntry.js diff --git a/liquidDevEntry.js b/liquidDevEntry.js new file mode 100644 index 0000000..92f887a --- /dev/null +++ b/liquidDevEntry.js @@ -0,0 +1,55 @@ +const context = require.context('./src', true, /\.liquid$/); + +const cache = {}; + +context.keys().forEach(function (key) { + cache[key] = context(key); +}); + +function replaceHtml(key, startCommentNode) { + const commentNodeType = startCommentNode.nodeType; + while ( + startCommentNode.nextSibling.nodeType !== commentNodeType || + !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) + ) { + startCommentNode.nextSibling.remove(); + } + + const tpl = document.createElement('template'); + tpl.innerHTML = cache[key]; + startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); +} + +if (module.hot) { + module.hot.accept(context.id, function () { + const newContext = require.context('./src', true, /\.liquid$/); + const changes = []; + newContext.keys().forEach(function (key) { + const newFile = newContext(key); + if (cache[key] !== newFile) { + changes.push(key); + cache[key] = newFile; + } + }); + + changes.forEach((changedFile) => { + traverseHMRComments(changedFile, replaceHtml); + }); + }); +} + +function traverseHMRComments(file, callback) { + const nodeIterator = document.createNodeIterator( + document.body, + NodeFilter.SHOW_COMMENT, + function (node) { + return node.nodeValue.includes(`hmr-start: ${file}`) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + } + ); + + while (nodeIterator.nextNode()) { + callback(file, nodeIterator.referenceNode); + } +} diff --git a/package.json b/package.json index 3ad3bd3..ccb3498 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "postcss-loader": "^4.0.4", "prettier": "^2.1.2", "sass-loader": "^10.1.0", + "string-loader": "^0.0.1", "style-loader": "^2.0.0", "tailwindcss": "^2.0.1", "transform-class-properties": "^1.0.0-beta", diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index 67cc6d3..127d214 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -31,9 +31,11 @@ {%- comment -%}Varibles{%- endcomment -%} {{ content_for_header }} {% include 'global-css' %} - + {{ 'tailwind.min.css' | asset_url | stylesheet_tag }} {{ 'bundle.global-css.css' | asset_url | stylesheet_tag }} + {{ 'bundle.runtime.js' | asset_url | script_tag }} + {{ 'bundle.liquidDevEntry.js' | asset_url | script_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index 086f4f5..4adc24f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -15,21 +15,31 @@ const publicPath = isDevMode ? `https://localhost:${port}/` : ''; module.exports = { stats: stats, - entry: glob.sync('./src/components/**/*.js').reduce((acc, path) => { - const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); - acc[entry] = path; - return acc; - }, {}), + entry: glob.sync('./src/components/**/*.js').reduce( + (acc, path) => { + const entry = path.replace(/^.*[\\\/]/, '').replace('.js', ''); + acc[entry] = path; + return acc; + }, + { liquidDevEntry: './liquidDevEntry.js' } + ), output: { - filename: './assets/bundle.[name].js', - hotUpdateChunkFilename: './hot/[id].[fullhash].hot-update.js', - hotUpdateMainFilename: './hot/[fullhash].hot-update.json', + filename: 'assets/bundle.[name].js', + hotUpdateChunkFilename: 'hot/[id].[fullhash].hot-update.js', + hotUpdateMainFilename: 'hot/[fullhash].hot-update.json', path: path.resolve(__dirname, 'dist'), publicPath, }, cache: false, + optimization: { + runtimeChunk: { name: 'runtime' }, + }, module: { rules: [ + { + test: /\.liquid$/, + use: ['string-loader'], + }, { test: /\.(sc|sa|c)ss$/, use: [ @@ -117,7 +127,10 @@ module.exports = { return path.join(targetFolder, path.basename(absolutePath)); }, transform: isDevMode - ? function (content) { + ? function (content, absolutePath) { + const relativePath = path.join(__dirname, 'src'); + const diff = path.relative(relativePath, absolutePath); + content = content .toString() .replace( @@ -134,7 +147,7 @@ module.exports = { } ); - return content; + return `${content}`; } : undefined, }, @@ -167,7 +180,6 @@ module.exports = { https: true, disableHostCheck: true, hot: true, - liveReload: false, overlay: true, writeToDisk: true, }, From 7ce4b0bdc4601c19775c1f851ed8bc25b9833241 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:26:13 +0200 Subject: [PATCH 19/35] Add basic liquid process in the pipeline --- liquidDevEntry.js => liquidDev.entry.js | 4 +- liquidDev.loader.js | 51 +++++++++++++++++++++++++ package.json | 2 + src/components/layout/theme.liquid | 2 +- webpack.config.js | 15 +++++--- 5 files changed, 66 insertions(+), 8 deletions(-) rename liquidDevEntry.js => liquidDev.entry.js (91%) create mode 100644 liquidDev.loader.js diff --git a/liquidDevEntry.js b/liquidDev.entry.js similarity index 91% rename from liquidDevEntry.js rename to liquidDev.entry.js index 92f887a..5276ca8 100644 --- a/liquidDevEntry.js +++ b/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /\.liquid$/); +const context = require.context('./src', true, /message\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /\.liquid$/); + const newContext = require.context('./src', true, /message\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/liquidDev.loader.js b/liquidDev.loader.js new file mode 100644 index 0000000..6af258e --- /dev/null +++ b/liquidDev.loader.js @@ -0,0 +1,51 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('liquidjs-section-tags'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('stylesheet_tag', function (v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin( + liquidSectionTags({ + root: liquidFiles, + }) +); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/package.json b/package.json index ccb3498..cddb400 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "file-loader": "^6.0.0", "glob": "^7.1.6", "html-webpack-plugin": "^4.3.0", + "liquidjs": "^9.16.1", + "liquidjs-section-tags": "^1.0.0", "mini-css-extract-plugin": "^1.3.1", "node-fetch": "^2.6.1", "node-sass": "^5.0.0", diff --git a/src/components/layout/theme.liquid b/src/components/layout/theme.liquid index 127d214..1e8cc9e 100644 --- a/src/components/layout/theme.liquid +++ b/src/components/layout/theme.liquid @@ -35,7 +35,7 @@ {{ 'tailwind.min.css' | asset_url | stylesheet_tag }} {{ 'bundle.global-css.css' | asset_url | stylesheet_tag }} {{ 'bundle.runtime.js' | asset_url | script_tag }} - {{ 'bundle.liquidDevEntry.js' | asset_url | script_tag }} + {{ 'bundle.liquidDev.js' | asset_url | script_tag }} {{ 'bundle.theme.js' | asset_url | script_tag }} diff --git a/webpack.config.js b/webpack.config.js index 4adc24f..dae9e10 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,7 +21,7 @@ module.exports = { acc[entry] = path; return acc; }, - { liquidDevEntry: './liquidDevEntry.js' } + { liquidDev: './liquidDev.entry.js' } ), output: { filename: 'assets/bundle.[name].js', @@ -36,9 +36,15 @@ module.exports = { }, module: { rules: [ - { + isDevMode && { test: /\.liquid$/, - use: ['string-loader'], + use: [ + 'string-loader', + { + loader: path.resolve(__dirname, 'liquidDev.loader.js'), + options: { publicPath }, + }, + ], }, { test: /\.(sc|sa|c)ss$/, @@ -72,8 +78,7 @@ module.exports = { exclude: /node_modules/, use: ["babel-loader"] }, - - ], + ].filter(Boolean), }, plugins: [ new CleanWebpackPlugin({ From 289ba439acc3df8f30b12e515eb848517dbc6577 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 00:49:33 +0200 Subject: [PATCH 20/35] Add support for sections & schema tags --- liquidDev.loader.js | 51 ---------------- package.json | 1 - .../liquidDev.entry.js | 4 +- shopify-dev-utils/liquidDev.loader.js | 59 +++++++++++++++++++ shopify-dev-utils/section-tags/index.d.ts | 2 + shopify-dev-utils/section-tags/index.js | 16 +++++ .../section-tags/javascript.d.ts | 2 + shopify-dev-utils/section-tags/javascript.js | 24 ++++++++ shopify-dev-utils/section-tags/schema.d.ts | 2 + shopify-dev-utils/section-tags/schema.js | 45 ++++++++++++++ shopify-dev-utils/section-tags/section.d.ts | 2 + shopify-dev-utils/section-tags/section.js | 30 ++++++++++ .../section-tags/stylesheet.d.ts | 2 + shopify-dev-utils/section-tags/stylesheet.js | 44 ++++++++++++++ shopify-dev-utils/transformLiquid.js | 31 ++++++++++ webpack.config.js | 39 ++++-------- 16 files changed, 273 insertions(+), 81 deletions(-) delete mode 100644 liquidDev.loader.js rename liquidDev.entry.js => shopify-dev-utils/liquidDev.entry.js (89%) create mode 100644 shopify-dev-utils/liquidDev.loader.js create mode 100644 shopify-dev-utils/section-tags/index.d.ts create mode 100644 shopify-dev-utils/section-tags/index.js create mode 100644 shopify-dev-utils/section-tags/javascript.d.ts create mode 100644 shopify-dev-utils/section-tags/javascript.js create mode 100644 shopify-dev-utils/section-tags/schema.d.ts create mode 100644 shopify-dev-utils/section-tags/schema.js create mode 100644 shopify-dev-utils/section-tags/section.d.ts create mode 100644 shopify-dev-utils/section-tags/section.js create mode 100644 shopify-dev-utils/section-tags/stylesheet.d.ts create mode 100644 shopify-dev-utils/section-tags/stylesheet.js create mode 100644 shopify-dev-utils/transformLiquid.js diff --git a/liquidDev.loader.js b/liquidDev.loader.js deleted file mode 100644 index 6af258e..0000000 --- a/liquidDev.loader.js +++ /dev/null @@ -1,51 +0,0 @@ -const loaderUtils = require('loader-utils'); -const path = require('path'); -const { Liquid } = require('liquidjs'); -const glob = require('glob'); -const { liquidSectionTags } = require('liquidjs-section-tags'); - -const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), -]; - -const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" -}); - -engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; -}); - -engine.registerFilter('stylesheet_tag', function (v) { - return ``; // in Dev mode we load css from js for HMR -}); - -engine.registerFilter('script_tag', function (v) { - return ``; -}); - -engine.plugin( - liquidSectionTags({ - root: liquidFiles, - }) -); - -module.exports = function (content) { - if (this.cacheable) this.cacheable(); - - engine.options.loaderOptions = loaderUtils.getOptions(this); - const callback = this.async(); - - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); -}; diff --git a/package.json b/package.json index cddb400..197a2f1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "glob": "^7.1.6", "html-webpack-plugin": "^4.3.0", "liquidjs": "^9.16.1", - "liquidjs-section-tags": "^1.0.0", "mini-css-extract-plugin": "^1.3.1", "node-fetch": "^2.6.1", "node-sass": "^5.0.0", diff --git a/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js similarity index 89% rename from liquidDev.entry.js rename to shopify-dev-utils/liquidDev.entry.js index 5276ca8..a5cdd11 100644 --- a/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /message\.liquid$/); +const context = require.context('../src', true, /(header|message)\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /message\.liquid$/); + const newContext = require.context('../src', true, /(header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js new file mode 100644 index 0000000..cf31d5f --- /dev/null +++ b/shopify-dev-utils/liquidDev.loader.js @@ -0,0 +1,59 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('./section-tags/index'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('paginate', function (_v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('stylesheet_tag', function (_v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin(liquidSectionTags()); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const { isSection } = engine.options.loaderOptions; + + // section handled specially + if (typeof isSection === 'function' && isSection(this.context)) { + const sectionName = path.basename(this.resourcePath, '.liquid'); + content = `{% section "${sectionName}" %}`; + } + + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/shopify-dev-utils/section-tags/index.d.ts b/shopify-dev-utils/section-tags/index.d.ts new file mode 100644 index 0000000..49e17b0 --- /dev/null +++ b/shopify-dev-utils/section-tags/index.d.ts @@ -0,0 +1,2 @@ +import { Liquid } from 'liquidjs'; +export declare function liquidSectionTags(): (this: Liquid) => void; diff --git a/shopify-dev-utils/section-tags/index.js b/shopify-dev-utils/section-tags/index.js new file mode 100644 index 0000000..d0f109e --- /dev/null +++ b/shopify-dev-utils/section-tags/index.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.liquidSectionTags = void 0; +const javascript_1 = require("./javascript"); +const schema_1 = require("./schema"); +const section_1 = require("./section"); +const stylesheet_1 = require("./stylesheet"); +function liquidSectionTags() { + return function () { + this.registerTag('section', section_1.Section); + this.registerTag('schema', schema_1.Schema); + this.registerTag('stylesheet', stylesheet_1.StyleSheet); + this.registerTag('javascript', javascript_1.JavaScript); + }; +} +exports.liquidSectionTags = liquidSectionTags; diff --git a/shopify-dev-utils/section-tags/javascript.d.ts b/shopify-dev-utils/section-tags/javascript.d.ts new file mode 100644 index 0000000..40a02a3 --- /dev/null +++ b/shopify-dev-utils/section-tags/javascript.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const JavaScript: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/javascript.js b/shopify-dev-utils/section-tags/javascript.js new file mode 100644 index 0000000..2c89529 --- /dev/null +++ b/shopify-dev-utils/section-tags/javascript.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JavaScript = void 0; +exports.JavaScript = { + parse: function (tagToken, remainTokens) { + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endjavascript') + stream.stop(); + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function () { + const text = this.tokens.map((token) => token.getText()).join(''); + return ``; + } +}; diff --git a/shopify-dev-utils/section-tags/schema.d.ts b/shopify-dev-utils/section-tags/schema.d.ts new file mode 100644 index 0000000..da4f996 --- /dev/null +++ b/shopify-dev-utils/section-tags/schema.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const Schema: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/schema.js b/shopify-dev-utils/section-tags/schema.js new file mode 100644 index 0000000..71744e2 --- /dev/null +++ b/shopify-dev-utils/section-tags/schema.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Schema = void 0; +function generateSettingsObj(settings) { + if (!Array.isArray(settings)) { + return settings; + } + return settings + .filter((entry) => !!entry.id) + .reduce((sectionSettings, entry) => { + sectionSettings[entry.id] = entry.default; + return sectionSettings; + }, {}); +} +exports.Schema = { + parse: function (tagToken, remainTokens) { + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endschema') { + stream.stop(); + } + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function (ctx) { + const json = this.tokens.map((token) => token.getText()).join(''); + const schema = JSON.parse(json); + const scope = ctx.scopes[ctx.scopes.length - 1]; + scope.section = { + settings: generateSettingsObj(schema.settings), + blocks: (schema.blocks || []).map((block) => ({ + ...block, + settings: generateSettingsObj(block.settings) + })) + }; + return ''; + } +}; diff --git a/shopify-dev-utils/section-tags/section.d.ts b/shopify-dev-utils/section-tags/section.d.ts new file mode 100644 index 0000000..7770b29 --- /dev/null +++ b/shopify-dev-utils/section-tags/section.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs'; +export declare const Section: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/section.js b/shopify-dev-utils/section-tags/section.js new file mode 100644 index 0000000..0baca53 --- /dev/null +++ b/shopify-dev-utils/section-tags/section.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Section = void 0; +const quoted = /^'[^']*'|"[^"]*"$/; +exports.Section = { + parse: function (token) { + this.namestr = token.args; + }, + render: function* (ctx, emitter) { + let name; + if (quoted.exec(this.namestr)) { + const template = this.namestr.slice(1, -1); + name = yield this.liquid._parseAndRender(template, ctx.getAll(), ctx.opts); + } + if (!name) + throw new Error('cannot include with empty filename'); + const templates = yield this.liquid._parseFile(name, ctx.opts, ctx.sync); + // Bubble up schema tag for allowing it's data available to the section + templates.sort((tagA) => { + return tagA.token.kind === 4 && + tagA.token.name === 'schema' + ? -1 + : 0; + }); + const scope = {}; + ctx.push(scope); + yield this.liquid.renderer.renderTemplates(templates, ctx, emitter); + ctx.pop(); + } +}; diff --git a/shopify-dev-utils/section-tags/stylesheet.d.ts b/shopify-dev-utils/section-tags/stylesheet.d.ts new file mode 100644 index 0000000..c09b425 --- /dev/null +++ b/shopify-dev-utils/section-tags/stylesheet.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs'; +export declare const StyleSheet: TagImplOptions; diff --git a/shopify-dev-utils/section-tags/stylesheet.js b/shopify-dev-utils/section-tags/stylesheet.js new file mode 100644 index 0000000..b44809c --- /dev/null +++ b/shopify-dev-utils/section-tags/stylesheet.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StyleSheet = void 0; +const sass_1 = require("sass"); +const quoted = /^'[^']*'|"[^"]*"$/; +const processors = { + '': (x) => x, + sass: sassProcessor, + scss: sassProcessor +}; +exports.StyleSheet = { + parse: function (token, remainTokens) { + this.processor = token.args; + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endstylesheet') + stream.stop(); + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${token.getText()} not closed`); + }); + stream.start(); + }, + render: async function (ctx) { + let processor = ''; + if (quoted.exec(this.processor)) { + const template = this.processor.slice(1, -1); + processor = await this.liquid.parseAndRender(template, ctx.getAll(), ctx.opts); + } + const text = this.tokens.map((token) => token.getText()).join(''); + const p = processors[processor]; + if (!p) + throw new Error(`processor for ${processor} not found`); + const css = await p(text); + return ``; + } +}; +function sassProcessor(data) { + return new Promise((resolve, reject) => sass_1.render({ data }, (err, result) => err ? reject(err) : resolve('' + result.css))); +} diff --git a/shopify-dev-utils/transformLiquid.js b/shopify-dev-utils/transformLiquid.js new file mode 100644 index 0000000..23d80ed --- /dev/null +++ b/shopify-dev-utils/transformLiquid.js @@ -0,0 +1,31 @@ +const path = require('path'); + +module.exports.transformLiquid = function transformLiquid(publicPath) { + return (content, absolutePath) => { + const relativePath = path.join(__dirname, '../src'); + const diff = path.relative(relativePath, absolutePath); + + content = content + .toString() + .replace( + /{{\s*'([^']+)'\s*\|\s*asset_url\s*\|\s*(stylesheet_tag|script_tag)\s*}}/g, + function (matched, fileName, type) { + if (type === 'stylesheet_tag') { + if (fileName !== 'tailwind.min.css') { + return ''; + } + return matched; + } + + return ``; + } + ); + + if(diff.includes('/layout/theme.liquid')) { + // inject HMR entry bundle + content = content.replace('',``) + } + + return `${content}`; + }; +} diff --git a/webpack.config.js b/webpack.config.js index dae9e10..060956b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const WebpackShellPluginNext = require('webpack-shell-plugin-next'); const ESLintPlugin = require('eslint-webpack-plugin'); +const { transformLiquid } = require('./shopify-dev-utils/transformLiquid'); const isDevMode = argv.mode === 'development'; @@ -21,7 +22,7 @@ module.exports = { acc[entry] = path; return acc; }, - { liquidDev: './liquidDev.entry.js' } + { liquidDev: './shopify-dev-utils/liquidDev.entry.js' } ), output: { filename: 'assets/bundle.[name].js', @@ -41,8 +42,15 @@ module.exports = { use: [ 'string-loader', { - loader: path.resolve(__dirname, 'liquidDev.loader.js'), - options: { publicPath }, + loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), + options: { + publicPath, + isSection(liquidPath) { + const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); + const componentType = diff.split(path.sep).shift(); + return componentType === 'sections'; + } + }, }, ], }, @@ -131,30 +139,7 @@ module.exports = { const targetFolder = diff.split(path.sep)[0]; return path.join(targetFolder, path.basename(absolutePath)); }, - transform: isDevMode - ? function (content, absolutePath) { - const relativePath = path.join(__dirname, 'src'); - const diff = path.relative(relativePath, absolutePath); - - content = content - .toString() - .replace( - /{{\s*'([^']+)'\s*\|\s*asset_url\s*\|\s*(stylesheet_tag|script_tag)\s*}}/g, - function (matched, fileName, type) { - if (type === 'stylesheet_tag') { - if (fileName !== 'tailwind.min.css') { - return ''; - } - return matched; - } - - return ``; - } - ); - - return `${content}`; - } - : undefined, + transform: isDevMode ? transformLiquid(publicPath) : undefined, }, { from: 'src/assets/**/*', From d2533c02cd3dcbb2bc05ee23001203cc9301da3e Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 21:23:25 +0200 Subject: [PATCH 21/35] Add static store data --- shopify-dev-utils/liquidDev.entry.js | 4 ++-- shopify-dev-utils/liquidDev.loader.js | 6 ++++-- shopify-dev-utils/storeData.js | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 shopify-dev-utils/storeData.js diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index a5cdd11..4eee04f 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('../src', true, /(header|message)\.liquid$/); +const context = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('../src', true, /(header|message)\.liquid$/); + const newContext = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index cf31d5f..46f8b84 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -2,6 +2,7 @@ const loaderUtils = require('loader-utils'); const path = require('path'); const { Liquid } = require('liquidjs'); const glob = require('glob'); +const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const liquidFiles = [ @@ -16,7 +17,8 @@ const liquidFiles = [ const engine = new Liquid({ root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" + extname: '.liquid', // used for layouts/includes, defaults "", + globals: fetchStoreData() }); engine.registerFilter('asset_url', function (v) { @@ -26,7 +28,7 @@ engine.registerFilter('asset_url', function (v) { }); engine.registerFilter('paginate', function (_v) { - return ``; // in Dev mode we load css from js for HMR + return ``; }); engine.registerFilter('stylesheet_tag', function (_v) { diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js new file mode 100644 index 0000000..83af7e2 --- /dev/null +++ b/shopify-dev-utils/storeData.js @@ -0,0 +1,26 @@ +module.exports.fetchStoreData = function fetchStoreData() { + + return { + 'shop': { + 'name': 'themekit-webpack-test' + }, + 'linklists': { + 'main-menu': { + 'title': '', + 'levels': 1, + 'links': [ + { + 'title': 'Home', + 'url': '/', + 'links': [] + }, + { + 'title': 'Catalog', + 'url': '/collections/all', + 'links': [] + } + ] + } + } + }; +}; From 5ff723d4d4054218d5ef7ffcff968bd4be1fbbcb Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Sat, 28 Nov 2020 16:41:35 +0200 Subject: [PATCH 22/35] Add support for paginate tag --- shopify-dev-utils/liquidDev.entry.js | 12 +++- shopify-dev-utils/liquidDev.loader.js | 58 ++++++++--------- shopify-dev-utils/tags/paginate.d.ts | 2 + shopify-dev-utils/tags/paginate.js | 93 +++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 shopify-dev-utils/tags/paginate.d.ts create mode 100644 shopify-dev-utils/tags/paginate.js diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index 4eee04f..bbbf9ab 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,8 @@ -const context = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); +const context = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ +); const cache = {}; @@ -22,7 +26,11 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('../src', true, /(footer|featured-product|featured-collection|header|message)\.liquid$/); + const newContext = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ + ); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 46f8b84..bfaed8b 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -4,58 +4,56 @@ const { Liquid } = require('liquidjs'); const glob = require('glob'); const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); +const { Paginate } = require('./tags/paginate'); const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), ]; const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "", - globals: fetchStoreData() + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "", + globals: fetchStoreData(), }); engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; + const { publicPath } = this.context.opts.loaderOptions; - return `${publicPath}${v}`; -}); - -engine.registerFilter('paginate', function (_v) { - return ``; + return `${publicPath}${v}`; }); engine.registerFilter('stylesheet_tag', function (_v) { - return ``; // in Dev mode we load css from js for HMR + return ``; // in Dev mode we load css from js for HMR }); engine.registerFilter('script_tag', function (v) { - return ``; + return ``; }); +engine.registerTag('paginate', Paginate); engine.plugin(liquidSectionTags()); module.exports = function (content) { - if (this.cacheable) this.cacheable(); + if (this.cacheable) this.cacheable(); - engine.options.loaderOptions = loaderUtils.getOptions(this); - const { isSection } = engine.options.loaderOptions; + engine.options.loaderOptions = loaderUtils.getOptions(this); + const { isSection } = engine.options.loaderOptions; - // section handled specially - if (typeof isSection === 'function' && isSection(this.context)) { - const sectionName = path.basename(this.resourcePath, '.liquid'); - content = `{% section "${sectionName}" %}`; - } + // section handled specially + if (typeof isSection === 'function' && isSection(this.context)) { + const sectionName = path.basename(this.resourcePath, '.liquid'); + content = `{% section "${sectionName}" %}`; + } - const callback = this.async(); + const callback = this.async(); - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); }; diff --git a/shopify-dev-utils/tags/paginate.d.ts b/shopify-dev-utils/tags/paginate.d.ts new file mode 100644 index 0000000..34abbfe --- /dev/null +++ b/shopify-dev-utils/tags/paginate.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const Paginate: TagImplOptions; diff --git a/shopify-dev-utils/tags/paginate.js b/shopify-dev-utils/tags/paginate.js new file mode 100644 index 0000000..f43bcb2 --- /dev/null +++ b/shopify-dev-utils/tags/paginate.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Paginate = void 0; +const liquidjs_1 = require("liquidjs"); +function generatePaginateObj({ offset, perPage, total }) { + const pages = Math.ceil(total / perPage); + const currentPage = Math.floor((offset + perPage) / perPage); + const paginate = { + current_offset: offset, + current_page: currentPage, + items: total, + page_size: perPage, + parts: Array(pages) + .fill(0) + .map((_, index) => { + const page = index + 1; + if (page === currentPage) { + return { title: page, is_link: false }; + } + return { title: page, url: `?page=${page}`, is_link: true }; + }), + pages, + previous: undefined, + next: undefined + }; + if (currentPage === pages && pages > 1) { + paginate.previous = { + title: '\u0026laquo; Previous', + url: `?page=${currentPage - 1}`, + is_link: true + }; + } + else if (currentPage < pages && pages > 1) { + paginate.next = { + title: 'Next \u0026raquo;', + url: `?page=${currentPage + 1}`, + is_link: true + }; + } + return paginate; +} +function populateVariableObj({ data, depth }) { + return depth.reverse().reduce((result, prop) => { + return { [prop.getText()]: result }; + }, data); +} +exports.Paginate = { + parse: function (tagToken, remainTokens) { + this.templates = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('start', () => { + const toknenizer = new liquidjs_1.Tokenizer(tagToken.args); + const list = toknenizer.readValue(); + const by = toknenizer.readWord(); + const perPage = toknenizer.readValue(); + liquidjs_1.assert(list.size() && + by.content === 'by' && + +perPage.getText() > 0 && + +perPage.getText() <= 50, () => `illegal tag: ${tagToken.getText()}`); + this.args = { list, perPage: +perPage.getText() }; + }) + .on('tag:endpaginate', () => stream.stop()) + .on('template', (tpl) => { + this.templates.push(tpl); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function* (ctx, emitter) { + const list = yield liquidjs_1.evalToken(this.args.list, ctx) || []; + const perPage = this.args.perPage; + const currentPage = +ctx.get(['current_page']); + const offset = currentPage ? (currentPage - 1) * perPage : 0; + const variableName = this.args.list.getVariableAsText(); + const scopeList = list.slice(offset, offset + perPage); + const data = populateVariableObj({ + data: scopeList, + depth: this.args.list.props + }); + const paginate = generatePaginateObj({ + offset, + perPage, + total: list.length + }); + const scope = { [variableName]: data, paginate }; + ctx.push(scope); + yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter); + ctx.pop(); + } +}; From 721db1197a767fa9e853b2ca13806bd43a7d27c6 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 25 Nov 2020 16:35:27 -0800 Subject: [PATCH 23/35] collection-list updated --- src/components/templates/list-collections.liquid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/templates/list-collections.liquid b/src/components/templates/list-collections.liquid index 23c9d3f..d5cdcfe 100644 --- a/src/components/templates/list-collections.liquid +++ b/src/components/templates/list-collections.liquid @@ -33,4 +33,4 @@ {%- endif -%} {%- endif -%} {%- endpaginate -%} - \ No newline at end of file + From 9893cdaa2c25e235f6dd4e0d1c33391714cfc291 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 27 Nov 2020 19:52:08 -0800 Subject: [PATCH 24/35] collections-list setup pagination --- src/config/settings_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/settings_schema.json b/src/config/settings_schema.json index d23f356..976b8f7 100644 --- a/src/config/settings_schema.json +++ b/src/config/settings_schema.json @@ -58,4 +58,4 @@ } ] } -] \ No newline at end of file +] From 3d61230998f92c5f8e7610e13be6cb3356f50d8c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 28 Nov 2020 12:39:13 -0800 Subject: [PATCH 25/35] ESLint added to webpack --- webpack.config.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 060956b..4ad37b1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,7 +8,6 @@ const WebpackShellPluginNext = require('webpack-shell-plugin-next'); const ESLintPlugin = require('eslint-webpack-plugin'); const { transformLiquid } = require('./shopify-dev-utils/transformLiquid'); - const isDevMode = argv.mode === 'development'; const stats = isDevMode ? 'errors-warnings' : { children: false }; const port = 9000; @@ -46,10 +45,13 @@ module.exports = { options: { publicPath, isSection(liquidPath) { - const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); + const diff = path.relative( + path.join(__dirname, './src/components/'), + liquidPath + ); const componentType = diff.split(path.sep).shift(); return componentType === 'sections'; - } + }, }, }, ], @@ -84,7 +86,7 @@ module.exports = { { test: /\.js$/, exclude: /node_modules/, - use: ["babel-loader"] + use: ['babel-loader'], }, ].filter(Boolean), }, @@ -93,7 +95,7 @@ module.exports = { cleanStaleWebpackAssets: false, }), new ESLintPlugin({ - fix: true + fix: true, }), isDevMode && new WebpackShellPluginNext({ From 6047ad945376e401cbd2806a5b26ea072f903c22 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:31:28 +0200 Subject: [PATCH 26/35] Finish phase 1, html-hmr --- liquidDevEntry.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 4 ++++ 2 files changed, 59 insertions(+) create mode 100644 liquidDevEntry.js diff --git a/liquidDevEntry.js b/liquidDevEntry.js new file mode 100644 index 0000000..92f887a --- /dev/null +++ b/liquidDevEntry.js @@ -0,0 +1,55 @@ +const context = require.context('./src', true, /\.liquid$/); + +const cache = {}; + +context.keys().forEach(function (key) { + cache[key] = context(key); +}); + +function replaceHtml(key, startCommentNode) { + const commentNodeType = startCommentNode.nodeType; + while ( + startCommentNode.nextSibling.nodeType !== commentNodeType || + !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) + ) { + startCommentNode.nextSibling.remove(); + } + + const tpl = document.createElement('template'); + tpl.innerHTML = cache[key]; + startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); +} + +if (module.hot) { + module.hot.accept(context.id, function () { + const newContext = require.context('./src', true, /\.liquid$/); + const changes = []; + newContext.keys().forEach(function (key) { + const newFile = newContext(key); + if (cache[key] !== newFile) { + changes.push(key); + cache[key] = newFile; + } + }); + + changes.forEach((changedFile) => { + traverseHMRComments(changedFile, replaceHtml); + }); + }); +} + +function traverseHMRComments(file, callback) { + const nodeIterator = document.createNodeIterator( + document.body, + NodeFilter.SHOW_COMMENT, + function (node) { + return node.nodeValue.includes(`hmr-start: ${file}`) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + } + ); + + while (nodeIterator.nextNode()) { + callback(file, nodeIterator.referenceNode); + } +} diff --git a/webpack.config.js b/webpack.config.js index 4ad37b1..7d521a9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,6 +56,10 @@ module.exports = { }, ], }, + { + test: /\.liquid$/, + use: ['string-loader'], + }, { test: /\.(sc|sa|c)ss$/, use: [ From c5978b932b111efe90504e0e857166b6679e8e7a Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:26:13 +0200 Subject: [PATCH 27/35] Add basic liquid process in the pipeline --- liquidDevEntry.js => liquidDev.entry.js | 4 +- liquidDev.loader.js | 51 +++++++++++++++++++++++++ webpack.config.js | 8 +++- 3 files changed, 60 insertions(+), 3 deletions(-) rename liquidDevEntry.js => liquidDev.entry.js (91%) create mode 100644 liquidDev.loader.js diff --git a/liquidDevEntry.js b/liquidDev.entry.js similarity index 91% rename from liquidDevEntry.js rename to liquidDev.entry.js index 92f887a..5276ca8 100644 --- a/liquidDevEntry.js +++ b/liquidDev.entry.js @@ -1,4 +1,4 @@ -const context = require.context('./src', true, /\.liquid$/); +const context = require.context('./src', true, /message\.liquid$/); const cache = {}; @@ -22,7 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /\.liquid$/); + const newContext = require.context('./src', true, /message\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/liquidDev.loader.js b/liquidDev.loader.js new file mode 100644 index 0000000..6af258e --- /dev/null +++ b/liquidDev.loader.js @@ -0,0 +1,51 @@ +const loaderUtils = require('loader-utils'); +const path = require('path'); +const { Liquid } = require('liquidjs'); +const glob = require('glob'); +const { liquidSectionTags } = require('liquidjs-section-tags'); + +const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), +]; + +const engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "" +}); + +engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}); + +engine.registerFilter('stylesheet_tag', function (v) { + return ``; // in Dev mode we load css from js for HMR +}); + +engine.registerFilter('script_tag', function (v) { + return ``; +}); + +engine.plugin( + liquidSectionTags({ + root: liquidFiles, + }) +); + +module.exports = function (content) { + if (this.cacheable) this.cacheable(); + + engine.options.loaderOptions = loaderUtils.getOptions(this); + const callback = this.async(); + + return engine + .parseAndRender(content, engine.options.loaderOptions.globals || {}) + .then((result) => callback(null, result)); +}; diff --git a/webpack.config.js b/webpack.config.js index 7d521a9..989714c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -58,7 +58,13 @@ module.exports = { }, { test: /\.liquid$/, - use: ['string-loader'], + use: [ + 'string-loader', + { + loader: path.resolve(__dirname, 'liquidDev.loader.js'), + options: { publicPath }, + }, + ], }, { test: /\.(sc|sa|c)ss$/, From f92ad980c2a662a743e72d25a1afdca8575f6fad Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Tue, 24 Nov 2020 00:49:33 +0200 Subject: [PATCH 28/35] Add support for sections & schema tags --- liquidDev.entry.js | 55 ---------------------------- liquidDev.loader.js | 51 -------------------------- shopify-dev-utils/liquidDev.entry.js | 12 +----- webpack.config.js | 11 +++++- 4 files changed, 11 insertions(+), 118 deletions(-) delete mode 100644 liquidDev.entry.js delete mode 100644 liquidDev.loader.js diff --git a/liquidDev.entry.js b/liquidDev.entry.js deleted file mode 100644 index 5276ca8..0000000 --- a/liquidDev.entry.js +++ /dev/null @@ -1,55 +0,0 @@ -const context = require.context('./src', true, /message\.liquid$/); - -const cache = {}; - -context.keys().forEach(function (key) { - cache[key] = context(key); -}); - -function replaceHtml(key, startCommentNode) { - const commentNodeType = startCommentNode.nodeType; - while ( - startCommentNode.nextSibling.nodeType !== commentNodeType || - !startCommentNode.nextSibling.nodeValue.includes(`hmr-end: ${key}`) - ) { - startCommentNode.nextSibling.remove(); - } - - const tpl = document.createElement('template'); - tpl.innerHTML = cache[key]; - startCommentNode.parentNode.insertBefore(tpl.content, startCommentNode.nextSibling); -} - -if (module.hot) { - module.hot.accept(context.id, function () { - const newContext = require.context('./src', true, /message\.liquid$/); - const changes = []; - newContext.keys().forEach(function (key) { - const newFile = newContext(key); - if (cache[key] !== newFile) { - changes.push(key); - cache[key] = newFile; - } - }); - - changes.forEach((changedFile) => { - traverseHMRComments(changedFile, replaceHtml); - }); - }); -} - -function traverseHMRComments(file, callback) { - const nodeIterator = document.createNodeIterator( - document.body, - NodeFilter.SHOW_COMMENT, - function (node) { - return node.nodeValue.includes(`hmr-start: ${file}`) - ? NodeFilter.FILTER_ACCEPT - : NodeFilter.FILTER_REJECT; - } - ); - - while (nodeIterator.nextNode()) { - callback(file, nodeIterator.referenceNode); - } -} diff --git a/liquidDev.loader.js b/liquidDev.loader.js deleted file mode 100644 index 6af258e..0000000 --- a/liquidDev.loader.js +++ /dev/null @@ -1,51 +0,0 @@ -const loaderUtils = require('loader-utils'); -const path = require('path'); -const { Liquid } = require('liquidjs'); -const glob = require('glob'); -const { liquidSectionTags } = require('liquidjs-section-tags'); - -const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(__dirname, path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), -]; - -const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "" -}); - -engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; -}); - -engine.registerFilter('stylesheet_tag', function (v) { - return ``; // in Dev mode we load css from js for HMR -}); - -engine.registerFilter('script_tag', function (v) { - return ``; -}); - -engine.plugin( - liquidSectionTags({ - root: liquidFiles, - }) -); - -module.exports = function (content) { - if (this.cacheable) this.cacheable(); - - engine.options.loaderOptions = loaderUtils.getOptions(this); - const callback = this.async(); - - return engine - .parseAndRender(content, engine.options.loaderOptions.globals || {}) - .then((result) => callback(null, result)); -}; diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index bbbf9ab..a5cdd11 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,8 +1,4 @@ -const context = require.context( - '../src', - true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ -); +const context = require.context('../src', true, /(header|message)\.liquid$/); const cache = {}; @@ -26,11 +22,7 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context( - '../src', - true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ - ); + const newContext = require.context('../src', true, /(header|message)\.liquid$/); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/webpack.config.js b/webpack.config.js index 989714c..450d9c4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -61,8 +61,15 @@ module.exports = { use: [ 'string-loader', { - loader: path.resolve(__dirname, 'liquidDev.loader.js'), - options: { publicPath }, + loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), + options: { + publicPath, + isSection(liquidPath) { + const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); + const componentType = diff.split(path.sep).shift(); + return componentType === 'sections'; + } + }, }, ], }, From 7a6217c68cb9f750806f940b9cb8bb243288ec62 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Mon, 7 Dec 2020 18:45:52 +0200 Subject: [PATCH 29/35] Fetch real store data --- .eslintignore | 2 + package.json | 5 +- shopify-dev-utils/liquidDev.entry.js | 12 +++- shopify-dev-utils/liquidDev.loader.js | 2 +- shopify-dev-utils/storeData.js | 72 ++++++++++++++-------- shopify-dev-utils/storefrontApi.js | 31 ++++++++++ src/components/sections/product/product.js | 4 +- src/components/templates/collection.liquid | 2 +- webpack.config.js | 19 +----- 9 files changed, 99 insertions(+), 50 deletions(-) create mode 100644 .eslintignore create mode 100644 shopify-dev-utils/storefrontApi.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..db46ba6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/dist/** +/shopify-dev-utils/** diff --git a/package.json b/package.json index 197a2f1..738f32e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@shopify/eslint-plugin": "^39.0.3", "@shopify/themekit": "^1.1.6", "autoprefixer": "^10.0.4", + "axios": "^0.21.0", "babel-eslint": "^10.1.0", "babel-loader": "^8.1.0", "babel-plugin-transform-class-properties": "^6.24.1", @@ -42,8 +43,9 @@ "postcss": "^8.1.10", "postcss-loader": "^4.0.4", "prettier": "^2.1.2", + "raw-loader": "^4.0.2", + "sass": "^1.29.0", "sass-loader": "^10.1.0", - "string-loader": "^0.0.1", "style-loader": "^2.0.0", "tailwindcss": "^2.0.1", "transform-class-properties": "^1.0.0-beta", @@ -52,6 +54,7 @@ "webpack-cli": "^4.2.0", "webpack-dev-server": "^3.11.0", "webpack-shell-plugin-next": "^2.0.8", + "yaml": "^1.10.0", "yargs": "^16.1.0" } } diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index a5cdd11..bbbf9ab 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,4 +1,8 @@ -const context = require.context('../src', true, /(header|message)\.liquid$/); +const context = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ +); const cache = {}; @@ -22,7 +26,11 @@ function replaceHtml(key, startCommentNode) { if (module.hot) { module.hot.accept(context.id, function () { - const newContext = require.context('../src', true, /(header|message)\.liquid$/); + const newContext = require.context( + '../src', + true, + /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ + ); const changes = []; newContext.keys().forEach(function (key) { const newFile = newContext(key); diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index bfaed8b..046806a 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -29,7 +29,7 @@ engine.registerFilter('asset_url', function (v) { }); engine.registerFilter('stylesheet_tag', function (_v) { - return ``; // in Dev mode we load css from js for HMR + return ''; // in Dev mode we load css from js for HMR }); engine.registerFilter('script_tag', function (v) { diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js index 83af7e2..ddb5906 100644 --- a/shopify-dev-utils/storeData.js +++ b/shopify-dev-utils/storeData.js @@ -1,26 +1,48 @@ -module.exports.fetchStoreData = function fetchStoreData() { +const yaml = require('yaml'); +const fs = require('fs'); +const path = require('path'); +const { StorefrontApi } = require('./storefrontApi'); - return { - 'shop': { - 'name': 'themekit-webpack-test' - }, - 'linklists': { - 'main-menu': { - 'title': '', - 'levels': 1, - 'links': [ - { - 'title': 'Home', - 'url': '/', - 'links': [] - }, - { - 'title': 'Catalog', - 'url': '/collections/all', - 'links': [] - } - ] - } - } - }; -}; +const configFile = path.join(__dirname, '../config.yml'); +let config = { token: '', baseURL: '' }; +if (fs.existsSync(configFile)) { + const configYml = yaml.parse(fs.readFileSync(configFile, 'utf-8')); + config.token = configYml.development.storefront_api_key; + config.baseURL = configYml.development.store; +} + +async function fetchStoreData() { + const storefrontApi = new StorefrontApi(config); + + const { data } = await storefrontApi.getStoreData(); + + console.log(JSON.stringify(data, null, 2)); + + return { + shop: { + name: data.shop.name, + }, + linklists: { + 'main-menu': { + title: '', + levels: 1, + links: [ + { + title: 'Home', + url: '/', + links: [], + }, + { + title: 'Catalog', + url: '/collections/all', + links: [], + }, + ], + }, + }, + }; +} + +fetchStoreData(); + +module.exports.fetchStoreData = fetchStoreData; diff --git a/shopify-dev-utils/storefrontApi.js b/shopify-dev-utils/storefrontApi.js new file mode 100644 index 0000000..0d38f30 --- /dev/null +++ b/shopify-dev-utils/storefrontApi.js @@ -0,0 +1,31 @@ +const Axios = require('axios'); + +class StorefrontApi { + constructor({ baseURL, token }) { + console.log(`https://${baseURL}/api/2020-10/graphql`); + this.axios = Axios.create({ + baseURL: `https://${baseURL}/api/2020-10/graphql`, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/graphql', + 'X-Shopify-Storefront-Access-Token': token, + }, + }); + } + + async getStoreData() { + return this.axios + .post( + '', + ` +query { + shop { + name + } +}` + ) + .then(({ data }) => data); + } +} + +module.exports.StorefrontApi = StorefrontApi; diff --git a/src/components/sections/product/product.js b/src/components/sections/product/product.js index 0eeabcb..0204c75 100644 --- a/src/components/sections/product/product.js +++ b/src/components/sections/product/product.js @@ -1,7 +1,7 @@ import {post} from '../../helpers/cart-fetch-api/cart-fetch-api'; document.getElementById('AddToCartForm').onsubmit = async function (event) { - const btn = document.getElementById('AddToCartBtn') + const btn = document.getElementById('AddToCartBtn'); const id = document.getElementById('AddToCartBtn').value; const option1 = document.getElementById('option1').value; const option2 = document.getElementById('option2').value; @@ -11,7 +11,7 @@ document.getElementById('AddToCartForm').onsubmit = async function (event) { const response = await post('add.js', {id, option1, option2, qty}); if(response) { - btn.textContent = 'ITEM ADDED' + btn.textContent = 'ITEM ADDED'; } return false; }; \ No newline at end of file diff --git a/src/components/templates/collection.liquid b/src/components/templates/collection.liquid index 3940917..63a09ae 100644 --- a/src/components/templates/collection.liquid +++ b/src/components/templates/collection.liquid @@ -33,4 +33,4 @@ {%- endif -%} {%- endif -%} {% endpaginate %} - \ No newline at end of file + diff --git a/webpack.config.js b/webpack.config.js index 450d9c4..9b3dad2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,7 +39,7 @@ module.exports = { isDevMode && { test: /\.liquid$/, use: [ - 'string-loader', + { loader: 'raw-loader', options: { esModule: false } }, { loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), options: { @@ -56,23 +56,6 @@ module.exports = { }, ], }, - { - test: /\.liquid$/, - use: [ - 'string-loader', - { - loader: path.resolve(__dirname, 'shopify-dev-utils/liquidDev.loader.js'), - options: { - publicPath, - isSection(liquidPath) { - const diff = path.relative(path.join(__dirname, './src/components/'), liquidPath); - const componentType = diff.split(path.sep).shift(); - return componentType === 'sections'; - } - }, - }, - ], - }, { test: /\.(sc|sa|c)ss$/, use: [ From 990840bdc785425d503c32b3974d19093cd35991 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 9 Dec 2020 11:32:21 +0200 Subject: [PATCH 30/35] Fix paginate bugs --- shopify-dev-utils/tags/paginate.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/shopify-dev-utils/tags/paginate.js b/shopify-dev-utils/tags/paginate.js index f43bcb2..a1730fc 100644 --- a/shopify-dev-utils/tags/paginate.js +++ b/shopify-dev-utils/tags/paginate.js @@ -39,10 +39,19 @@ function generatePaginateObj({ offset, perPage, total }) { } return paginate; } -function populateVariableObj({ data, depth }) { - return depth.reverse().reduce((result, prop) => { - return { [prop.getText()]: result }; - }, data); +function populateVariableObj({ list, originalValue, depth }) { + if (depth.length === 0) { + return list; + } + const clone = JSON.parse(JSON.stringify(originalValue)); + depth.reduce((result, prop, index) => { + const propName = prop.getText(); + if (index === depth.length - 1) { + result[propName] = list; + } + return result[propName] || {}; + }, clone); + return clone; } exports.Paginate = { parse: function (tagToken, remainTokens) { @@ -75,9 +84,11 @@ exports.Paginate = { const currentPage = +ctx.get(['current_page']); const offset = currentPage ? (currentPage - 1) * perPage : 0; const variableName = this.args.list.getVariableAsText(); + const originalValue = ctx.get([variableName]); const scopeList = list.slice(offset, offset + perPage); const data = populateVariableObj({ - data: scopeList, + list: scopeList, + originalValue, depth: this.args.list.props }); const paginate = generatePaginateObj({ From 6db8f4f5788dcf99c0402cb2d1f3a0ff8a5ec9b9 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Wed, 9 Dec 2020 11:34:00 +0200 Subject: [PATCH 31/35] Add store front data fetcher --- .../convertToGlobalDataStructure.js | 33 +++++++ shopify-dev-utils/liquidDev.loader.js | 90 +++++++++++-------- shopify-dev-utils/storeData.js | 14 +-- shopify-dev-utils/storefrontApi.js | 52 ++++++++++- src/components/templates/collection.liquid | 21 +++-- 5 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 shopify-dev-utils/convertToGlobalDataStructure.js diff --git a/shopify-dev-utils/convertToGlobalDataStructure.js b/shopify-dev-utils/convertToGlobalDataStructure.js new file mode 100644 index 0000000..9c557b1 --- /dev/null +++ b/shopify-dev-utils/convertToGlobalDataStructure.js @@ -0,0 +1,33 @@ +module.exports.convertToGlobalDataStructure = function convertToGlobalDataStructure(gqlData) { + // return gqlData; + return { + shop: { + name: gqlData.shop.name, + }, + collections: gqlData.collections.edges.map(({ node }) => ({ + title: node.title, + id: node.id, + handle: node.handle, + image: node.image, + description: node.description, + url: 'unknown-url', // TODO: find a way to get the collection url + products: node.products.edges.map((product) => ({ + id: product.node.id, + title: product.node.title, + description: product.node.description, + handle: product.node.handle, + available: product.node.availableForSale, + price: product.node.priceRange, + url: 'unknown-url', // TODO: find a way to get the product url + featured_image: + product.node.images.edges.length > 0 + ? { + id: product.node.images.edges[0].node.id, + alt: product.node.images.edges[0].node.altText, + src: product.node.images.edges[0].node.originalSrc, + } + : null, + })), + })), + }; +}; diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 046806a..d99ae17 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -6,41 +6,61 @@ const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const { Paginate } = require('./tags/paginate'); -const liquidFiles = [ - ...glob - .sync('./src/components/**/*.liquid') - .map((filePath) => path.resolve(path.join(__dirname, '../'), path.dirname(filePath))) - .reduce((set, dir) => { - set.add(dir); - return set; - }, new Set()), -]; - -const engine = new Liquid({ - root: liquidFiles, // root for layouts/includes lookup - extname: '.liquid', // used for layouts/includes, defaults "", - globals: fetchStoreData(), -}); - -engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; -}); - -engine.registerFilter('stylesheet_tag', function (_v) { - return ''; // in Dev mode we load css from js for HMR -}); - -engine.registerFilter('script_tag', function (v) { - return ``; -}); - -engine.registerTag('paginate', Paginate); -engine.plugin(liquidSectionTags()); - -module.exports = function (content) { +let engine; +let loadPromise; + +function initEngine() { + if (!loadPromise) { + loadPromise = new Promise(async (resolve) => { + const liquidFiles = [ + ...glob + .sync('./src/components/**/*.liquid') + .map((filePath) => + path.resolve(path.join(__dirname, '../'), path.dirname(filePath)) + ) + .reduce((set, dir) => { + set.add(dir); + return set; + }, new Set()), + ]; + + engine = new Liquid({ + root: liquidFiles, // root for layouts/includes lookup + extname: '.liquid', // used for layouts/includes, defaults "", + globals: await fetchStoreData(), + }); + + engine.registerFilter('asset_url', function (v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; + }); + + engine.registerFilter('stylesheet_tag', function (_v) { + return ''; // in Dev mode we load css from js for HMR + }); + + engine.registerFilter('script_tag', function (v) { + return ``; + }); + + engine.registerTag('paginate', Paginate); + engine.plugin(liquidSectionTags()); + + resolve(); + }); + } + + return loadPromise; +} + +module.exports = async function (content) { if (this.cacheable) this.cacheable(); + const callback = this.async(); + + if (!engine) { + await initEngine(); + } engine.options.loaderOptions = loaderUtils.getOptions(this); const { isSection } = engine.options.loaderOptions; @@ -51,8 +71,6 @@ module.exports = function (content) { content = `{% section "${sectionName}" %}`; } - const callback = this.async(); - return engine .parseAndRender(content, engine.options.loaderOptions.globals || {}) .then((result) => callback(null, result)); diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js index ddb5906..9189779 100644 --- a/shopify-dev-utils/storeData.js +++ b/shopify-dev-utils/storeData.js @@ -1,6 +1,7 @@ const yaml = require('yaml'); const fs = require('fs'); const path = require('path'); +const { convertToGlobalDataStructure } = require('./convertToGlobalDataStructure'); const { StorefrontApi } = require('./storefrontApi'); const configFile = path.join(__dirname, '../config.yml'); @@ -9,14 +10,18 @@ if (fs.existsSync(configFile)) { const configYml = yaml.parse(fs.readFileSync(configFile, 'utf-8')); config.token = configYml.development.storefront_api_key; config.baseURL = configYml.development.store; + + if (!config.token) { + console.warn(`'storefront_api_key' was not found in 'config.yml'`); + } } async function fetchStoreData() { const storefrontApi = new StorefrontApi(config); - const { data } = await storefrontApi.getStoreData(); - - console.log(JSON.stringify(data, null, 2)); + const data = await storefrontApi + .getStoreData() + .then(({ data }) => convertToGlobalDataStructure(data)); return { shop: { @@ -40,9 +45,8 @@ async function fetchStoreData() { ], }, }, + collection: data.collections[0], }; } -fetchStoreData(); - module.exports.fetchStoreData = fetchStoreData; diff --git a/shopify-dev-utils/storefrontApi.js b/shopify-dev-utils/storefrontApi.js index 0d38f30..43febaf 100644 --- a/shopify-dev-utils/storefrontApi.js +++ b/shopify-dev-utils/storefrontApi.js @@ -18,11 +18,59 @@ class StorefrontApi { .post( '', ` -query { +{ shop { name } -}` + collections(first: 50) { + edges { + node { + id + title + handle + description + image(scale:1) { + id + altText + originalSrc + transformedSrc + } + products(first: 50) { + edges { + node { + id + title + description + handle + availableForSale + priceRange { + maxVariantPrice { + amount + currencyCode + } + minVariantPrice { + amount + currencyCode + } + } + images(first: 1) { + edges { + node { + id + altText + originalSrc + } + } + } + onlineStoreUrl + } + } + } + } + } + } +} +` ) .then(({ data }) => data); } diff --git a/src/components/templates/collection.liquid b/src/components/templates/collection.liquid index 63a09ae..5643b18 100644 --- a/src/components/templates/collection.liquid +++ b/src/components/templates/collection.liquid @@ -1,16 +1,16 @@
{% paginate collection.products by 8 %} -

{{ collection.title }}

-
From 3726a9345a7c03844c6116b8915b80f49017c79e Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Fri, 25 Dec 2020 13:04:24 +0200 Subject: [PATCH 32/35] Add default_pagination filter Refactor filters to separate files --- shopify-dev-utils/filters/asset_url.js | 5 ++++ .../filters/default_pagination.js | 26 +++++++++++++++++++ shopify-dev-utils/filters/script_tag.js | 3 +++ shopify-dev-utils/filters/stylesheet_tag.js | 3 +++ shopify-dev-utils/liquidDev.loader.js | 21 ++++++--------- 5 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 shopify-dev-utils/filters/asset_url.js create mode 100644 shopify-dev-utils/filters/default_pagination.js create mode 100644 shopify-dev-utils/filters/script_tag.js create mode 100644 shopify-dev-utils/filters/stylesheet_tag.js diff --git a/shopify-dev-utils/filters/asset_url.js b/shopify-dev-utils/filters/asset_url.js new file mode 100644 index 0000000..dcaf2ae --- /dev/null +++ b/shopify-dev-utils/filters/asset_url.js @@ -0,0 +1,5 @@ +module.exports.assetUrl = function assetUrl(v) { + const { publicPath } = this.context.opts.loaderOptions; + + return `${publicPath}${v}`; +}; diff --git a/shopify-dev-utils/filters/default_pagination.js b/shopify-dev-utils/filters/default_pagination.js new file mode 100644 index 0000000..781612b --- /dev/null +++ b/shopify-dev-utils/filters/default_pagination.js @@ -0,0 +1,26 @@ +module.exports.defaultPagination = function defaultPagination(paginate, ...rest) { + const next = rest.filter((arg) => arg[0] === 'next').pop(); + const previous = rest.filter((arg) => arg[0] === 'previous').pop(); + + const prevLabel = + previous.length > 0 ? previous.pop() : paginate.previous ? paginate.previous.title : ''; + const nextLabel = next.length > 0 ? next.pop() : paginate.next ? paginate.next.title : ''; + + const prevPart = paginate.previous + ? `${prevLabel}` + : ''; + const nextPart = paginate.next + ? `${nextLabel}` + : ''; + + const pagesPart = paginate.parts + .map((part) => { + if (part.is_link) { + return `${part.title}`; + } + return `${part.title}`; + }) + .join(''); + + return `${prevPart}${pagesPart}${nextPart}`; +}; diff --git a/shopify-dev-utils/filters/script_tag.js b/shopify-dev-utils/filters/script_tag.js new file mode 100644 index 0000000..f555324 --- /dev/null +++ b/shopify-dev-utils/filters/script_tag.js @@ -0,0 +1,3 @@ +module.exports.scriptTag = function scriptTag(v) { + return ``; +}; diff --git a/shopify-dev-utils/filters/stylesheet_tag.js b/shopify-dev-utils/filters/stylesheet_tag.js new file mode 100644 index 0000000..a69fb92 --- /dev/null +++ b/shopify-dev-utils/filters/stylesheet_tag.js @@ -0,0 +1,3 @@ +module.exports.stylesheetTag = function stylesheetTag() { + return ''; // in Dev mode we load css from js for HMR +}; diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index d99ae17..a91fa5d 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -2,6 +2,10 @@ const loaderUtils = require('loader-utils'); const path = require('path'); const { Liquid } = require('liquidjs'); const glob = require('glob'); +const { defaultPagination } = require('./filters/default_pagination'); +const { scriptTag } = require('./filters/script_tag'); +const { stylesheetTag } = require('./filters/stylesheet_tag'); +const { assetUrl } = require('./filters/asset_url'); const { fetchStoreData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const { Paginate } = require('./tags/paginate'); @@ -30,19 +34,10 @@ function initEngine() { globals: await fetchStoreData(), }); - engine.registerFilter('asset_url', function (v) { - const { publicPath } = this.context.opts.loaderOptions; - - return `${publicPath}${v}`; - }); - - engine.registerFilter('stylesheet_tag', function (_v) { - return ''; // in Dev mode we load css from js for HMR - }); - - engine.registerFilter('script_tag', function (v) { - return ``; - }); + engine.registerFilter('asset_url', assetUrl); + engine.registerFilter('stylesheet_tag', stylesheetTag); + engine.registerFilter('script_tag', scriptTag); + engine.registerFilter('default_pagination', defaultPagination); engine.registerTag('paginate', Paginate); engine.plugin(liquidSectionTags()); From 8e9a58d5739266313e51a68569096f0b59c38ff2 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Fri, 25 Dec 2020 13:04:48 +0200 Subject: [PATCH 33/35] Add global settings from settings_schema file --- shopify-dev-utils/liquidDev.loader.js | 4 ++-- shopify-dev-utils/storeData.js | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index a91fa5d..2c5393c 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -6,7 +6,7 @@ const { defaultPagination } = require('./filters/default_pagination'); const { scriptTag } = require('./filters/script_tag'); const { stylesheetTag } = require('./filters/stylesheet_tag'); const { assetUrl } = require('./filters/asset_url'); -const { fetchStoreData } = require('./storeData'); +const { getStoreGlobalData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const { Paginate } = require('./tags/paginate'); @@ -31,7 +31,7 @@ function initEngine() { engine = new Liquid({ root: liquidFiles, // root for layouts/includes lookup extname: '.liquid', // used for layouts/includes, defaults "", - globals: await fetchStoreData(), + globals: await getStoreGlobalData(), }); engine.registerFilter('asset_url', assetUrl); diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js index 9189779..1c9d8a4 100644 --- a/shopify-dev-utils/storeData.js +++ b/shopify-dev-utils/storeData.js @@ -16,7 +16,22 @@ if (fs.existsSync(configFile)) { } } -async function fetchStoreData() { +function getGlobalSettings() { + const rawSettings = require('../src/config/settings_schema.json'); + + return rawSettings + .filter((section) => !!section.settings) + .reduce((result, section) => { + section.settings + .filter((setting) => !!setting.id && typeof setting.default !== 'undefined') + .forEach((setting) => { + result[setting.id] = setting.default; + }); + return result; + }, {}); +} + +async function getStoreGlobalData() { const storefrontApi = new StorefrontApi(config); const data = await storefrontApi @@ -27,6 +42,7 @@ async function fetchStoreData() { shop: { name: data.shop.name, }, + settings: getGlobalSettings(), linklists: { 'main-menu': { title: '', @@ -49,4 +65,4 @@ async function fetchStoreData() { }; } -module.exports.fetchStoreData = fetchStoreData; +module.exports.getStoreGlobalData = getStoreGlobalData; From cdcd35fedbd7d068e78c2ef02896980bc91a4bab Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Fri, 25 Dec 2020 13:17:36 +0200 Subject: [PATCH 34/35] Add within filter --- shopify-dev-utils/convertToGlobalDataStructure.js | 4 ++-- shopify-dev-utils/filters/within.js | 3 +++ shopify-dev-utils/liquidDev.loader.js | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 shopify-dev-utils/filters/within.js diff --git a/shopify-dev-utils/convertToGlobalDataStructure.js b/shopify-dev-utils/convertToGlobalDataStructure.js index 9c557b1..b298fa9 100644 --- a/shopify-dev-utils/convertToGlobalDataStructure.js +++ b/shopify-dev-utils/convertToGlobalDataStructure.js @@ -10,7 +10,7 @@ module.exports.convertToGlobalDataStructure = function convertToGlobalDataStruct handle: node.handle, image: node.image, description: node.description, - url: 'unknown-url', // TODO: find a way to get the collection url + url: `/collections/${node.handle}`, products: node.products.edges.map((product) => ({ id: product.node.id, title: product.node.title, @@ -18,7 +18,7 @@ module.exports.convertToGlobalDataStructure = function convertToGlobalDataStruct handle: product.node.handle, available: product.node.availableForSale, price: product.node.priceRange, - url: 'unknown-url', // TODO: find a way to get the product url + url: `/products/${product.node.handle}`, featured_image: product.node.images.edges.length > 0 ? { diff --git a/shopify-dev-utils/filters/within.js b/shopify-dev-utils/filters/within.js new file mode 100644 index 0000000..aa117ce --- /dev/null +++ b/shopify-dev-utils/filters/within.js @@ -0,0 +1,3 @@ +module.exports.within = function within(productUrl, collection) { + return `${collection.url}${productUrl}`; +}; diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 2c5393c..6dacc8a 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -2,6 +2,7 @@ const loaderUtils = require('loader-utils'); const path = require('path'); const { Liquid } = require('liquidjs'); const glob = require('glob'); +const { within } = require('./filters/within'); const { defaultPagination } = require('./filters/default_pagination'); const { scriptTag } = require('./filters/script_tag'); const { stylesheetTag } = require('./filters/stylesheet_tag'); @@ -38,6 +39,7 @@ function initEngine() { engine.registerFilter('stylesheet_tag', stylesheetTag); engine.registerFilter('script_tag', scriptTag); engine.registerFilter('default_pagination', defaultPagination); + engine.registerFilter('within', within); engine.registerTag('paginate', Paginate); engine.plugin(liquidSectionTags()); From 3c229d9950fd0d4e96caa09c193fb76e3ae63f4a Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Fri, 25 Dec 2020 14:47:56 +0200 Subject: [PATCH 35/35] Add money filters --- shopify-dev-utils/convertToGlobalDataStructure.js | 7 ++++++- shopify-dev-utils/filters/money_with_currency.js | 13 +++++++++++++ .../filters/money_without_trailing_zeros.js | 14 ++++++++++++++ shopify-dev-utils/liquidDev.loader.js | 4 ++++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 shopify-dev-utils/filters/money_with_currency.js create mode 100644 shopify-dev-utils/filters/money_without_trailing_zeros.js diff --git a/shopify-dev-utils/convertToGlobalDataStructure.js b/shopify-dev-utils/convertToGlobalDataStructure.js index b298fa9..2c69370 100644 --- a/shopify-dev-utils/convertToGlobalDataStructure.js +++ b/shopify-dev-utils/convertToGlobalDataStructure.js @@ -17,7 +17,12 @@ module.exports.convertToGlobalDataStructure = function convertToGlobalDataStruct description: product.node.description, handle: product.node.handle, available: product.node.availableForSale, - price: product.node.priceRange, + price: product.node.priceRange.maxVariantPrice, // preserve the entire obj for money-* filters + price_max: product.node.priceRange.maxVariantPrice, + price_min: product.node.priceRange.minVariantPrice, + price_varies: + +product.node.priceRange.maxVariantPrice.amount !== + +product.node.priceRange.minVariantPrice, url: `/products/${product.node.handle}`, featured_image: product.node.images.edges.length > 0 diff --git a/shopify-dev-utils/filters/money_with_currency.js b/shopify-dev-utils/filters/money_with_currency.js new file mode 100644 index 0000000..784825c --- /dev/null +++ b/shopify-dev-utils/filters/money_with_currency.js @@ -0,0 +1,13 @@ +module.exports.moneyWithCurrency = function moneyWithCurrency(price) { + if (!price || !price.currencyCode || !price.amount) { + return ''; + } + + // the price that this object gets has 2 fields it is not the same value in "real" env, + // at real it should be only a number multiplied by 100 + return new Intl.NumberFormat('en', { + style: 'currency', + currency: price.currencyCode, + maximumFractionDigits: 2, + }).format(price.amount); +}; diff --git a/shopify-dev-utils/filters/money_without_trailing_zeros.js b/shopify-dev-utils/filters/money_without_trailing_zeros.js new file mode 100644 index 0000000..0e0f2e7 --- /dev/null +++ b/shopify-dev-utils/filters/money_without_trailing_zeros.js @@ -0,0 +1,14 @@ +const { moneyWithCurrency } = require('./money_with_currency'); + +module.exports.moneyWithoutTrailingZeros = function moneyWithoutTrailingZeros(price) { + // the price that this object gets has 2 fields it is not the same value in "real" env, + // at real it should be only a number multiplied by 100 + const moneyWithCurrencyAndTrailingZeros = moneyWithCurrency(price); + return moneyWithCurrencyAndTrailingZeros.replace( + /([,.][^0]*(0+))\D*$/, + (match, group, zeros) => { + const cutSize = zeros.length > 1 ? zeros.length + 1 : zeros.length; + return match.replace(group, group.substring(0, group.length - cutSize)); + } + ); +}; diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 6dacc8a..427441d 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -2,6 +2,8 @@ const loaderUtils = require('loader-utils'); const path = require('path'); const { Liquid } = require('liquidjs'); const glob = require('glob'); +const { moneyWithoutTrailingZeros } = require('./filters/money_without_trailing_zeros'); +const { moneyWithCurrency } = require('./filters/money_with_currency'); const { within } = require('./filters/within'); const { defaultPagination } = require('./filters/default_pagination'); const { scriptTag } = require('./filters/script_tag'); @@ -40,6 +42,8 @@ function initEngine() { engine.registerFilter('script_tag', scriptTag); engine.registerFilter('default_pagination', defaultPagination); engine.registerFilter('within', within); + engine.registerFilter('money_with_currency', moneyWithCurrency); + engine.registerFilter('money_without_trailing_zeros', moneyWithoutTrailingZeros); engine.registerTag('paginate', Paginate); engine.plugin(liquidSectionTags());