|
| 1 | +const { readFileSync } = require('fs') |
| 2 | +const { dirname, resolve } = require('path') |
| 3 | +const stripIndent = require('strip-indent') |
| 4 | + |
| 5 | +const { findPackageJson, getLanguage, runPreprocessor } = require('./utils.js') |
| 6 | + |
| 7 | +const throwError = (msg) => { throw new Error(`[svelte-smart-preprocess] ${msg}`) } |
| 8 | +const throwUnsupportedError = (lang, filename) => |
| 9 | + throwError(`Unsupported script language '${lang}' in file '${filename}'`) |
| 10 | + |
| 11 | +module.exports = (languages = {}) => { |
| 12 | + return { |
| 13 | + markup: ({content, filename}) => { |
| 14 | + const templateMatch = content.match(/(<template([\s\S]*?)>([\s\S]*?)<\/template>)/) |
| 15 | + |
| 16 | + /** If no <template> was found, just return the original markup */ |
| 17 | + if (!templateMatch) { |
| 18 | + return { code: content } |
| 19 | + } |
| 20 | + |
| 21 | + let [, wholeTemplateTag, unparsedAttrs, templateCode] = templateMatch |
| 22 | + |
| 23 | + /** Transform the <template> attributes into a consumable object */ |
| 24 | + unparsedAttrs = unparsedAttrs.trim() |
| 25 | + const attributes = unparsedAttrs.length > 0 |
| 26 | + ? unparsedAttrs.split(' ').reduce((acc, entry) => { |
| 27 | + const [key, value] = entry.split('=') |
| 28 | + acc[key] = value.replace(/['"]/g, '') |
| 29 | + return acc |
| 30 | + }, {}) |
| 31 | + : {} |
| 32 | + |
| 33 | + const lang = getLanguage(attributes, 'html') |
| 34 | + const componentDir = dirname(filename) |
| 35 | + |
| 36 | + /** If src="" is allowed and was defined, let's get the referenced file content */ |
| 37 | + if (attributes.src) { |
| 38 | + templateCode = readFileSync(resolve(componentDir, attributes.src)).toString() |
| 39 | + } |
| 40 | + |
| 41 | + /** If language is HTML, just remove the <template></template> tags */ |
| 42 | + if (lang === 'html') { |
| 43 | + return { |
| 44 | + code: content.replace(wholeTemplateTag, templateCode) |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + if (languages[lang]) { |
| 49 | + const processedContent = runPreprocessor(lang, |
| 50 | + languages[lang], |
| 51 | + stripIndent(templateCode), |
| 52 | + filename |
| 53 | + ) |
| 54 | + |
| 55 | + /** It may return a promise, let's check for that */ |
| 56 | + if (Promise.resolve(processedContent) === processedContent) { |
| 57 | + return processedContent.then(({ code }) => { |
| 58 | + return { |
| 59 | + code: content.replace(wholeTemplateTag, code) |
| 60 | + } |
| 61 | + }) |
| 62 | + } |
| 63 | + |
| 64 | + /** Remove the <template> tag with the actual template code */ |
| 65 | + return { |
| 66 | + code: content.replace(wholeTemplateTag, processedContent.code) |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + throwUnsupportedError(lang, filename) |
| 71 | + }, |
| 72 | + script: ({ content = '', attributes, filename }) => { |
| 73 | + const lang = getLanguage(attributes, 'javascript') |
| 74 | + const componentDir = dirname(filename) |
| 75 | + |
| 76 | + /** If src="" is allowed and was defined, let's get the referenced file content */ |
| 77 | + if (attributes.src) { |
| 78 | + content = readFileSync(resolve(componentDir, attributes.src)).toString() |
| 79 | + } |
| 80 | + |
| 81 | + if (lang === 'javascript') { |
| 82 | + return { code: content } |
| 83 | + } |
| 84 | + |
| 85 | + if (languages[lang]) { |
| 86 | + return runPreprocessor(lang, languages[lang], content, filename) |
| 87 | + } |
| 88 | + |
| 89 | + throwUnsupportedError(lang, filename) |
| 90 | + }, |
| 91 | + style: ({ content = '', attributes, filename }) => { |
| 92 | + const lang = getLanguage(attributes, 'css') |
| 93 | + const componentDir = dirname(filename) |
| 94 | + |
| 95 | + /** If src="" is allowed and was defined, let's get the referenced file content */ |
| 96 | + if (attributes.src) { |
| 97 | + content = readFileSync(resolve(componentDir, attributes.src)).toString() |
| 98 | + } |
| 99 | + |
| 100 | + if (lang === 'css') { |
| 101 | + return { code: content } |
| 102 | + } |
| 103 | + |
| 104 | + /** If the build step is supporting the used language, run it's preprocessor */ |
| 105 | + if (languages[lang]) { |
| 106 | + return runPreprocessor(lang, languages[lang], content, filename) |
| 107 | + } else { |
| 108 | + throwUnsupportedError(lang, filename) |
| 109 | + /** |
| 110 | + * If including a uncompiled svelte component which uses a not supported style language, |
| 111 | + * search for it's package.json to see if there's a valid css file defined with 'svelte.style' or 'style'. |
| 112 | + * */ |
| 113 | + const { data: pkgData, filename: pkgFilepath } = findPackageJson( |
| 114 | + componentDir |
| 115 | + ) |
| 116 | + |
| 117 | + /** Found any package data? Is it a svelte component? */ |
| 118 | + if (pkgData && pkgData.svelte) { |
| 119 | + const pkgDir = dirname(pkgFilepath) |
| 120 | + const svelteStyle = pkgData['svelte.style'] || pkgData.style |
| 121 | + |
| 122 | + /** |
| 123 | + * If there's a valid style definition, get the defined file's content. |
| 124 | + * |
| 125 | + * TODO - This is broken when the css has global styles. Disabled for now. |
| 126 | + * */ |
| 127 | + if (false && svelteStyle) { // eslint-disable-line |
| 128 | + // log( |
| 129 | + // 'info', |
| 130 | + // `Using precompiled css (${svelteStyle}) in component "${ |
| 131 | + // pkgData.name |
| 132 | + // }"` |
| 133 | + // ) |
| 134 | + content = readFileSync(resolve(pkgDir, svelteStyle)) |
| 135 | + .toString() |
| 136 | + .replace(/\.svelte-\w{4,7}/, '') |
| 137 | + } else { |
| 138 | + throwUnsupportedError(lang, filename) |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | +} |
0 commit comments