UNPKG

@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
/** * 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', }) } }, } }, }