@vant/cli
Version:
Vant CLI is a tool for building vue component library. You can quickly build a full-featured Vue component library with vant-cli.
109 lines (108 loc) • 4.11 kB
JavaScript
import fse from 'fs-extra';
import path from 'node:path';
import hash from 'hash-sum';
import { parse, compileTemplate, compileScript, compileStyle, } from 'vue/compiler-sfc';
import { replaceExt } from '../common/index.js';
const { remove, readFileSync, outputFile } = fse;
const RENDER_FN = '__vue_render__';
const VUEIDS = '__vue_sfc__';
const EXPORT = 'export default';
// trim some unused code
function trim(code) {
return code.replace(/\/\/\n/g, '').trim();
}
function getSfcStylePath(filePath, ext, index) {
const number = index !== 0 ? `-${index + 1}` : '';
return replaceExt(filePath, `-sfc${number}.${ext}`);
}
// inject render fn to script
function injectRender(script, render) {
script = trim(script);
render = render.replace('export function render', `function ${RENDER_FN}`);
script += `\n${render}\n${VUEIDS}.render = ${RENDER_FN} \n`;
return script;
}
function injectScopeId(script, scopeId) {
script += `\n${VUEIDS}.__scopeId = '${scopeId}'`;
return script;
}
function injectStyle(script, styles, filePath) {
if (styles.length) {
const imports = styles
.map((style, index) => {
const { base } = path.parse(getSfcStylePath(filePath, 'css', index));
return `import './${base}';`;
})
.join('\n');
script = `${imports}\n${script}`;
return script;
}
return script;
}
export function parseSfc(filename) {
const source = readFileSync(filename, 'utf-8');
const { descriptor } = parse(source, {
filename,
});
return descriptor;
}
export async function compileSfc(filePath) {
var _a, _b;
const tasks = [remove(filePath)];
const source = readFileSync(filePath, 'utf-8');
const descriptor = parseSfc(filePath);
const { template, styles } = descriptor;
const hasScoped = styles.some((s) => s.scoped);
const scopeId = hasScoped ? hash(source) : '';
const scopeKey = scopeId ? `data-v-${scopeId}` : '';
// compile js part
if (descriptor.script || descriptor.scriptSetup) {
const lang = ((_a = descriptor.script) === null || _a === void 0 ? void 0 : _a.lang) || ((_b = descriptor.scriptSetup) === null || _b === void 0 ? void 0 : _b.lang) || 'js';
const scriptFilePath = replaceExt(filePath, `.${lang}`);
tasks.push(new Promise((resolve) => {
let script = '';
const { bindings, content } = compileScript(descriptor, {
id: scopeKey,
});
script += content;
script = injectStyle(script, styles, filePath);
script = script.replace(EXPORT, `const ${VUEIDS} =`);
if (template) {
const render = compileTemplate({
id: scopeKey,
source: template.content,
scoped: !!scopeKey,
filename: filePath,
compilerOptions: {
scopeId: scopeKey,
bindingMetadata: bindings,
},
}).code;
script = injectRender(script, render);
}
if (scopeKey) {
script = injectScopeId(script, scopeKey);
}
script += `\n${EXPORT} ${VUEIDS}`;
// ts-nocheck should be placed on the first line
// the generated render fn lacks type definitions
if (lang === 'ts') {
script = '// @ts-nocheck\n' + script;
}
outputFile(scriptFilePath, script).then(resolve);
}));
}
// compile style part
tasks.push(...styles.map(async (style, index) => {
const cssFilePath = getSfcStylePath(filePath, style.lang || 'css', index);
const styleSource = compileStyle({
source: style.content,
filename: path.basename(cssFilePath),
scoped: style.scoped,
id: scopeId,
preprocessLang: style.lang,
});
return outputFile(cssFilePath, trim(styleSource.code));
}));
return Promise.all(tasks);
}