UNPKG

reboost

Version:

A super fast dev server for rapid web development

229 lines (218 loc) 9.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runtimeCode = exports.generateModuleCode = exports.getPlugins = void 0; const tslib_1 = require("tslib"); const modules_1 = require("./modules"); const path_1 = (0, tslib_1.__importDefault)(require("path")); const parsers_1 = require("./parsers"); const camelCase = (string) => string.replace(/(?:^\w|[A-Z]|\b\w)/g, (match, index) => (index === 0 ? match.toLowerCase() : match.toUpperCase())).replace(/(\s|-|_)+/g, ''); let idIndex = 0; const idMap = new Map(); const getID = (key) => { if (!idMap.has(key)) idMap.set(key, idIndex++); return idMap.get(key); }; const getPlugins = (options) => { const extracted = { imports: [], urls: [], icss: undefined }; const plugins = []; if (options.module) { if (options.module.hasValues) plugins.push((0, modules_1.moduleValues)()); plugins.push((0, modules_1.localByDefault)({ mode: options.module.mode }), (0, modules_1.extractImports)(), (0, modules_1.moduleScope)({ generateScopedName: (exportedName) => `_${exportedName}_${getID(exportedName + options.filePath)}_`, exportGlobals: options.module.exportGlobals }), { postcssPlugin: 'icss-extractor', Once(root) { extracted.icss = (0, modules_1.extractICSS)(root, true); } }); } if (options.handleImports) { plugins.push((0, parsers_1.importParser)(extracted.imports, (url) => options.testers.import(url, options.filePath))); } if (options.handleURLS) { plugins.push((0, parsers_1.urlParser)(extracted.urls, (url) => options.testers.url(url, options.filePath))); } return { plugins, extracted }; }; exports.getPlugins = getPlugins; const normalizeURL = (url) => { if (url.startsWith('~')) { url = url.substring(1); } else if (!url.startsWith('/') && !url.startsWith('./')) { url = './' + url; } return url; }; // Checks if a import is from postcss-module-value // CSS -> `@value <someValue> from "./file.module.css";` const importedValueRE = /i__const_/i; const generateModuleCode = (data) => { let code = ''; let defaultExportObjStr = '{}'; const replacements = {}; if (data.module) { const localNameMap = {}; const importsMap = {}; const { icssImports, icssExports } = data.module.icss; Object.keys(icssExports).forEach((key, _, keys) => { const camelCased = camelCase(key); if (!keys.includes(camelCased)) icssExports[camelCased] = icssExports[key]; }); Object.keys(icssImports).forEach((key, idx) => { if (idx === 0) code += '// ICSS imports\n'; const localName = 'icss_import_' + idx; localNameMap[key] = localName; code += `import ${localName} from ${JSON.stringify(key)};\n`; Object.keys(icssImports[key]).forEach((importName) => { importsMap[importName] = { from: key, name: icssImports[key][importName] }; if (importedValueRE.test(importName)) { replacements[importName] = `${localName}[${JSON.stringify(importsMap[importName].name)}]`; } }); }); const valueMap = {}; // Stringifies the JS object defaultExportObjStr = '{\n' + Object.keys(icssExports).map((key) => { const value = icssExports[key]; if (typeof valueMap[value] === 'undefined') { valueMap[value] = '`' + value.split(' ').map((token) => { const importData = importsMap[token]; if (importData) { return `\${${localNameMap[importData.from]}[${JSON.stringify(importData.name)}]}`; } return token; }).join(' ') + '`'; } return ` ${JSON.stringify(key)}: ${valueMap[value]},`; }).join('\n') + '\n}'; } code += `const defaultExport = ${defaultExportObjStr};\n`; const preCode = `\n/* ${path_1.default.relative(data.config.rootDir, data.filePath).replace(/\\/g, '/')} */\n\n`; let cssStr = preCode; cssStr += data.css; if (data.sourceMap) { // Fix the source map because we are prepend-ing some codes which are not mapped data.sourceMap.mappings = ';'.repeat(preCode.match(/\n/g).length) + data.sourceMap.mappings; cssStr += '\n\n/*# sourceMappingURL=data:application/json;charset=utf-8;base64,'; cssStr += Buffer.from(JSON.stringify(data.sourceMap)).toString('base64'); cssStr += ' */'; } if (data.urls.length) { code += '\n\n// Used URLs\n'; data.urls.forEach(({ url, replacement }, idx) => { url = normalizeURL(url); const localName = `url_${idx}`; code += `import ${localName} from ${JSON.stringify(url)};\n`; replacements[replacement] = localName; }); } // Stringify replacement object with unquoted values // -> { "replacementName": "identifierName" } -> { "replacementName": identifierName } const replacementKeys = Object.keys(replacements); const replacementObjStr = replacementKeys.length ? ('{\n' + replacementKeys.map((key) => (` ${JSON.stringify(key)}: ${replacements[key]},`)).join('\n') + '\n}') : '{}'; code += ` // Main style injection and its hot reload import { replaceReplacements, patchObject } from '#/css-runtime'; const updateListeners = new Set(); let exportedCSS = replaceReplacements(${JSON.stringify(cssStr)}, ${replacementObjStr}); export const toString = () => exportedCSS; Object.defineProperty(defaultExport, 'toString', { value: toString }); let style; const removeStyle = () => { if (!style) return; style.remove(); style = undefined; } if (!import.meta.hot.data) { style = document.createElement('style'); style.textContent = exportedCSS; document.head.appendChild(style); updateListeners.add(({ default: newDefaultExport, toString }) => { if (style) style.textContent = toString(); exportedCSS = toString(); patchObject(defaultExport, newDefaultExport); }); import.meta.hot.accept((...args) => updateListeners.forEach((cb) => cb(...args))); } export default defaultExport; export { removeStyle as __removeStyle, updateListeners as __updateListeners } `; if (data.imports.length) { code += ` // Imported style sheets import { ImportedStyle } from '#/css-runtime'; let importedStyles = []; `; data.imports.forEach(({ url, media }, idx) => { url = normalizeURL(url); const localName = 'atImport_' + idx; code += `import * as ${localName} from ${JSON.stringify(url)};\n`; code += `importedStyles.push(ImportedStyle(${localName}, ${JSON.stringify(media)}));\n`; }); code += ` if (!import.meta.hot.data) { importedStyles.forEach(({ apply }) => apply()); updateListeners.add(({ __importedStyles }) => { importedStyles.forEach(({ destroy }) => destroy()); if (__importedStyles) { __importedStyles.forEach(({ apply }) => apply()); importedStyles = __importedStyles; } }); } export const __importedStyles = importedStyles; `; } return code; }; exports.generateModuleCode = generateModuleCode; exports.runtimeCode = ` export const replaceReplacements = (str, replacements) => { Object.keys(replacements).forEach((toReplace) => { str = str.replace(new RegExp(toReplace, 'g'), replacements[toReplace]); }); return str; } export const patchObject = (object, target) => { Object.keys(object).forEach((key) => { if (!(key in target)) delete object[key]; }); Object.assign(object, target); } // This function removes the default style injected by the module // and handles the style with media export const ImportedStyle = (module, media) => { let style; const listener = ({ toString }) => (style.textContent = toString()); return { apply() { if (style) return; module.__removeStyle(); style = document.createElement('style'); style.textContent = module.toString(); if (media) style.media = media; document.head.appendChild(style); module.__updateListeners.add(listener); }, destroy() { if (!style) return; style.remove(); module.__updateListeners.delete(listener); } } } `;