@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
125 lines (108 loc) • 4.07 kB
JavaScript
/**
* ESLint rule to detect hardcoded local asset paths that won't work in production
* @fileoverview Prevents using un-imported hardcoded asset paths that work in Vite dev but fail in production
*/
const {
ASSET_EXTENSIONS,
LOCAL_PATH_PATTERNS,
REMOTE_PATTERNS,
// eslint-disable-next-line import/extensions
} = require('./asset-path-patterns.cjs')
/**
* Check if a string looks like a local asset path
* @param {string} value - The string to check
* @returns {boolean}
*/
function isLocalAssetPath(value) {
if (typeof value !== 'string') return false
// Must have an asset extension
if (!ASSET_EXTENSIONS.test(value)) return false
// Skip remote URLs and data URLs
if (REMOTE_PATTERNS.some(pattern => pattern.test(value))) return false
// Check if it looks like a local path
return LOCAL_PATH_PATTERNS.some(pattern => pattern.test(value))
}
/**
* Check if a template literal is definitely a local asset path
* Returns true only if we can determine with certainty it's a local path
* (starts with a local path pattern AND ends with an asset extension)
* @param {object} node - The TemplateLiteral AST node
* @returns {boolean}
*/
function isLocalAssetTemplateLiteral(node) {
const quasis = node.quasis
if (quasis.length === 0) return false
// Get the first static part to check if it starts with a local path
const firstPart = quasis[0].value.raw || quasis[0].value.cooked || ''
// Get the last static part to check if it ends with an asset extension
const lastPart =
quasis[quasis.length - 1].value.raw ||
quasis[quasis.length - 1].value.cooked ||
''
// Must end with an asset extension
if (!ASSET_EXTENSIONS.test(lastPart)) return false
// Check if it starts with a remote URL (only need to check first static part)
if (REMOTE_PATTERNS.some(pattern => pattern.test(firstPart))) return false
// Must start with a local path pattern (this makes it a definite error)
return LOCAL_PATH_PATTERNS.some(pattern => pattern.test(firstPart))
}
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Disallow hardcoded local asset paths that work in dev but fail in production',
category: 'Possible Errors',
recommended: true,
url: 'https://vite.dev/guide/assets',
},
messages: {
noHardcodedAssetPath:
'Hardcoded asset path "{{path}}" will not work in production. Import the asset instead: `import asset from "{{path}}"` then use the imported variable. See: https://vite.dev/guide/assets',
noHardcodedAssetPathTemplate:
'Template literal contains a hardcoded local asset path that will not work in production. Import assets instead of constructing paths. See: https://vite.dev/guide/assets',
},
schema: [],
},
create(context) {
return {
Literal(node) {
// Only check string literals
if (typeof node.value !== 'string') return
// Skip import/export declarations - those are the CORRECT way to use assets
const parent = node.parent
if (
parent &&
(parent.type === 'ImportDeclaration' ||
parent.type === 'ExportNamedDeclaration' ||
parent.type === 'ExportAllDeclaration' ||
(parent.type === 'CallExpression' &&
parent.callee &&
(parent.callee.name === 'require' ||
parent.callee.type === 'Import')))
) {
return
}
if (isLocalAssetPath(node.value)) {
context.report({
node,
messageId: 'noHardcodedAssetPath',
data: {
path: node.value,
},
})
}
},
TemplateLiteral(node) {
// Only flag template literals that are DEFINITELY local asset paths
// (start with local path pattern AND end with asset extension)
if (isLocalAssetTemplateLiteral(node)) {
context.report({
node,
messageId: 'noHardcodedAssetPathTemplate',
})
}
},
}
},
}