bigcommerce / stencil-cli

BigCommerce Stencil emulator for local theme development
https://developer.bigcommerce.com/stencil-docs
BSD 4-Clause "Original" or "Old" License
101 stars 141 forks source link

Error: The partial components/products/grid could not be found #397

Open flyingL123 opened 6 years ago

flyingL123 commented 6 years ago

I'm using stencil CLI 1.15.1 with node 7.9.0 on Mac High Sierra 10.13.1. I'm running into a very odd error that I haven't been able to debug.

In templates/pages/cart.html, I call a best sellers partial like this:

{{> components/cart/best-sellers}}

Within components/cart/best-sellers.html, I am calling my product grid partial, and attempting to pass it a json string as one of its arguments:

{{> components/products/grid
      products=products.top_sellers
      carousel='{"small": {"slides": 1.5}, "smallplus": {"slides": 2.5}, "medium": {"slides": 3}, "large": {"slides": 4}, "xlarge": {"slides": 5}}'
}}

I then load the cart page in the browser, and get the following error:

Debug: internal, implementation, error Error: The partial components/products/grid could not be found at Object.invokePartial (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/handlebars/dist/cjs/handlebars/runtime.js:216:11) at Object.invokePartialWrapper [as invokePartial] (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/nodemodules/handlebars/dist/cjs/handlebars/runtime.js:61:39) at Object.main (eval at .each (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/@bigcommerce/stencil-paper/index.js:130:21), :5:23) at ret (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/handlebars/dist/cjs/handlebars/runtime.js:159:30) at Object.invokePartial (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/handlebars/dist/cjs/handlebars/runtime.js:218:12) at Object.invokePartialWrapper [as invokePartial] (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/nodemodules/handlebars/dist/cjs/handlebars/runtime.js:61:39) at Object.1 (eval at .each (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/@bigcommerce/stencil-paper/index.js:130:21), :19:23) at prog (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/handlebars/dist/cjs/handlebars/runtime.js:193:15) at Object. (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/nodemodules/@bigcommerce/stencil-paper/helpers/block.js:9:16) at Object.main (eval at .each (/Users/me/.nvm/versions/node/v7.9.0/lib/node_modules/@bigcommerce/stencil-cli/node_modules/@bigcommerce/stencil-paper/index.js:130:21), :9:72)

If I remove the json string from the argument, the error goes away:

{{> components/products/grid
      products=products.top_sellers
      carousel=''
}}

The strange thing is that I'm using this method in many other places throughout my theme, and it doesn't give me any issues. For example, in templates/pages/home, I call the partial:

{{> components/products/top}}

And that file contains:

{{> components/products/grid
    products=products.top_sellers
    carousel='{"small": {"slides": 1.5}, "smallplus": {"slides": 2.5}, "medium": {"slides": 4}}'
}}

This works perfectly fine. I can't understand why there is an issue on the cart page, but not any other pages.

As part of debugging, I added the following code at line 214 of stencil-cli/node_modules/handlebars/dist/cjs/handlebars/runtime.js in order to see which partials were contained in the options object:

if (options.name === 'components/products/grid') {
    console.log('================OPTIONS===============');
    console.log(options);
}

When an empty string is used and no error occurs, I get the following output:

================OPTIONS===============
{ name: 'components/products/grid',
  hash:
   { carousel: '',
     qsSource: 'cart_best_sellers',
     smallplusCards: 'vertical',
     onBg: true,
     products:
      [ [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object] ] },
  data: {},
  helpers:
   { helperMissing: [Function],
     blockHelperMissing: [Function],
     each: [Function],
     if: [Function],
     unless: [Function],
     with: [Function],
     log: [Function],
     lookup: [Function],
     all: [Function],
     any: [Function],
     block: [Function],
     partial: [Function],
     cdn: [Function],
     compare: [Function],
     concat: [Function],
     contains: [Function],
     pick: [Function],
     getShortMonth: [Function],
     equals: [Function],
     enumerate: [Function],
     dynamicComponent: [Function],
     for: [Function],
     getFontLoaderConfig: [Function],
     getFontsCollection: [Function],
     getImage: [Function],
     inject: [Function],
     jsContext: [Function],
     join: [Function],
     json: [Function],
     lang: [Function],
     langJson: [Function],
     limit: [Function],
     money: [Function],
     nl2br: [Function],
     or: [Function],
     pluck: [Function],
     pre: [Function],
     region: [Function],
     replace: [Function],
     resourceHints: [Function],
     snippet: [Function],
     stylesheet: [Function],
     after: [Function],
     arrayify: [Function],
     before: [Function],
     eachIndex: [Function],
     filter: [Function],
     first: [Function],
     forEach: [Function],
     inArray: [Function],
     isArray: [Function],
     last: [Function],
     lengthEqual: [Function],
     map: [Function],
     some: [Function],
     sort: [Function],
     sortBy: [Function],
     withAfter: [Function],
     withBefore: [Function],
     withFirst: [Function],
     withLast: [Function],
     withSort: [Function],
     isEmpty: [Function],
     iterate: [Function],
     length: [Function],
     and: [Function],
     gt: [Function],
     gte: [Function],
     has: [Function],
     eq: [Function],
     ifEven: [Function],
     ifNth: [Function],
     ifOdd: [Function],
     is: [Function],
     isnt: [Function],
     lt: [Function],
     lte: [Function],
     neither: [Function],
     unlessEq: [Function],
     unlessGt: [Function],
     unlessLt: [Function],
     unlessGteq: [Function],
     unlessLteq: [Function],
     moment: [Function: momentHelper],
     ellipsis: [Function],
     sanitize: [Function],
     ul: [Function],
     ol: [Function],
     thumbnailImage: [Function],
     inflect: [Function],
     ordinalize: [Function],
     markdown: [Function: helper],
     add: [Function],
     subtract: [Function],
     divide: [Function],
     multiply: [Function],
     floor: [Function],
     ceil: [Function],
     round: [Function],
     sum: [Function],
     avg: [Function],
     default: [Function],
     option: [Function],
     noop: [Function],
     withHash: [Function],
     addCommas: [Function],
     phoneNumber: [Function],
     random: [Function],
     toAbbr: [Function],
     toExponential: [Function],
     toFixed: [Function],
     toFloat: [Function],
     toInt: [Function],
     toPrecision: [Function],
     extend: [Function],
     forIn: [Function],
     forOwn: [Function],
     toPath: [Function],
     get: [Function],
     getObject: [Function],
     hasOwn: [Function],
     isObject: [Function],
     merge: [Function],
     JSONparse: [Function],
     JSONstringify: [Function],
     camelcase: [Function],
     capitalize: [Function],
     capitalizeAll: [Function],
     center: [Function],
     chop: [Function],
     dashcase: [Function],
     dotcase: [Function],
     hyphenate: [Function],
     isString: [Function],
     lowercase: [Function],
     occurrences: [Function],
     pascalcase: [Function],
     pathcase: [Function],
     plusify: [Function],
     reverse: [Function],
     sentence: [Function],
     snakecase: [Function],
     split: [Function],
     startsWith: [Function],
     titleize: [Function],
     trim: [Function],
     uppercase: [Function],
     encodeURI: [Function],
     decodeURI: [Function],
     urlResolve: [Function],
     urlParse: [Function],
     stripQuerystring: [Function],
     stripProtocol: [Function],
     toLowerCase: [Function],
     truncate: [Function] },
  partials:
   { 'pages/cart': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/primary': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/totals': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/content': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/help-block': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/callouts': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/suggested': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/best-sellers': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/country-alert-modal': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'layout/base': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/status-messages': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/summary': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/buttons': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/coupon-input': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/gift-certificate-input': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/shipping-estimator': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/grid': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/analytics-head': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/icons/icon-defs': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/scroll-top': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/body': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/footer': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/international-modal': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/analytics-foot': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/card': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/accessory-card': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/free-shipping-promo': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/top-promo': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/top-banner': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header-info': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/main-navigation': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/scorpion-sticker': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/social-links': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/subscription-form': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/footer-link-list': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/copyright': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/product-card-price': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/ratings': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/product-card-bullets': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/search-box': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header-help-menu': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/category-navigation-link': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/products/product-card-free-shipping': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     page: { [Function: prog] program: 1, depth: 0, blockParams: 0 },
     gaPageview: { [Function: prog] program: 8, depth: 0, blockParams: 0 },
     fbEvent: { [Function: prog] program: 10, depth: 0, blockParams: 0 } },
  partial: true }

However, when I pass in the json string, I get this output, which is missing some partials, including components/products/grid':

================OPTIONS===============
{ name: 'components/products/grid',
  hash:
   { carousel: '{"small": {"slides": 1.5}, "smallplus": {"slides": 2.5}, "medium": {"slides": 3}, "large": {"slides": 4}, "xlarge": {"slides": 5}}',
     qsSource: 'cart_best_sellers',
     smallplusCards: 'vertical',
     onBg: true,
     products:
      [ [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object],
        [Object] ] },
  data: {},
  helpers:
   { helperMissing: [Function],
     blockHelperMissing: [Function],
     each: [Function],
     if: [Function],
     unless: [Function],
     with: [Function],
     log: [Function],
     lookup: [Function],
     all: [Function],
     any: [Function],
     block: [Function],
     partial: [Function],
     cdn: [Function],
     compare: [Function],
     concat: [Function],
     contains: [Function],
     pick: [Function],
     getShortMonth: [Function],
     equals: [Function],
     enumerate: [Function],
     dynamicComponent: [Function],
     for: [Function],
     getFontLoaderConfig: [Function],
     getFontsCollection: [Function],
     getImage: [Function],
     inject: [Function],
     jsContext: [Function],
     join: [Function],
     json: [Function],
     lang: [Function],
     langJson: [Function],
     limit: [Function],
     money: [Function],
     nl2br: [Function],
     or: [Function],
     pluck: [Function],
     pre: [Function],
     region: [Function],
     replace: [Function],
     resourceHints: [Function],
     snippet: [Function],
     stylesheet: [Function],
     after: [Function],
     arrayify: [Function],
     before: [Function],
     eachIndex: [Function],
     filter: [Function],
     first: [Function],
     forEach: [Function],
     inArray: [Function],
     isArray: [Function],
     last: [Function],
     lengthEqual: [Function],
     map: [Function],
     some: [Function],
     sort: [Function],
     sortBy: [Function],
     withAfter: [Function],
     withBefore: [Function],
     withFirst: [Function],
     withLast: [Function],
     withSort: [Function],
     isEmpty: [Function],
     iterate: [Function],
     length: [Function],
     and: [Function],
     gt: [Function],
     gte: [Function],
     has: [Function],
     eq: [Function],
     ifEven: [Function],
     ifNth: [Function],
     ifOdd: [Function],
     is: [Function],
     isnt: [Function],
     lt: [Function],
     lte: [Function],
     neither: [Function],
     unlessEq: [Function],
     unlessGt: [Function],
     unlessLt: [Function],
     unlessGteq: [Function],
     unlessLteq: [Function],
     moment: [Function: momentHelper],
     ellipsis: [Function],
     sanitize: [Function],
     ul: [Function],
     ol: [Function],
     thumbnailImage: [Function],
     inflect: [Function],
     ordinalize: [Function],
     markdown: [Function: helper],
     add: [Function],
     subtract: [Function],
     divide: [Function],
     multiply: [Function],
     floor: [Function],
     ceil: [Function],
     round: [Function],
     sum: [Function],
     avg: [Function],
     default: [Function],
     option: [Function],
     noop: [Function],
     withHash: [Function],
     addCommas: [Function],
     phoneNumber: [Function],
     random: [Function],
     toAbbr: [Function],
     toExponential: [Function],
     toFixed: [Function],
     toFloat: [Function],
     toInt: [Function],
     toPrecision: [Function],
     extend: [Function],
     forIn: [Function],
     forOwn: [Function],
     toPath: [Function],
     get: [Function],
     getObject: [Function],
     hasOwn: [Function],
     isObject: [Function],
     merge: [Function],
     JSONparse: [Function],
     JSONstringify: [Function],
     camelcase: [Function],
     capitalize: [Function],
     capitalizeAll: [Function],
     center: [Function],
     chop: [Function],
     dashcase: [Function],
     dotcase: [Function],
     hyphenate: [Function],
     isString: [Function],
     lowercase: [Function],
     occurrences: [Function],
     pascalcase: [Function],
     pathcase: [Function],
     plusify: [Function],
     reverse: [Function],
     sentence: [Function],
     snakecase: [Function],
     split: [Function],
     startsWith: [Function],
     titleize: [Function],
     trim: [Function],
     uppercase: [Function],
     encodeURI: [Function],
     decodeURI: [Function],
     urlResolve: [Function],
     urlParse: [Function],
     stripQuerystring: [Function],
     stripProtocol: [Function],
     toLowerCase: [Function],
     truncate: [Function] },
  partials:
   { 'pages/cart': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/primary': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/content': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/totals': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/help-block': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/suggested': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/callouts': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/best-sellers': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/country-alert-modal': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'layout/base': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/status-messages': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/summary': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/buttons': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/coupon-input': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/gift-certificate-input': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/cart/shipping-estimator': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/analytics-head': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/scroll-top': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/icons/icon-defs': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/body': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/footer': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/international-modal': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/analytics-foot': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/free-shipping-promo': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/top-promo': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/top-banner': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header-info': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/main-navigation': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/scorpion-sticker': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/social-links': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/subscription-form': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/footer-link-list': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/copyright': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/search-box': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/header-help-menu': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     'components/common/category-navigation-link': { [Function: ret] isTop: true, _setup: [Function], _child: [Function] },
     page: { [Function: prog] program: 1, depth: 0, blockParams: 0 },
     gaPageview: { [Function: prog] program: 8, depth: 0, blockParams: 0 },
     fbEvent: { [Function: prog] program: 10, depth: 0, blockParams: 0 } },
  partial: true }

Can someone explain what is causing this to occur?

flyingL123 commented 6 years ago

I also just uninstalled and reinstalled stencil cli. Still getting the same error.

flyingL123 commented 6 years ago

Ok, I think I found the issue. The partialRegex in lib/template-assembler.js doesn't pick up the partial if there is a json string used as an argument. The reason it was "working" for me on some pages is because I was including the products/grid partial elsewhere without the json string (when the grid is not being used as a carousel). This caused the parser to pick up that partial, so it worked even though it was picking up the partial from elsewhere on the page.

It looks like the regex needs to be improved so that it picks up a json string, since it's still just a string and I think should be valid.

As a seriously ugly workaround, if I just include a commented out call to the partial at the top of the page, it works, because the assembler still picks it up. For example, at the top of templates/pages/cart.html I have:

{{!-- {{> components/products/grid }} --}}

This 'fixes' the issue.