reboost
Version:
A super fast dev server for rapid web development
229 lines (218 loc) • 9.05 kB
JavaScript
;
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);
}
}
}
`;