I tried to use @vue/compiler-sfc to compile .vue files, I did succeed, but only if the code is placed in <script> tags, when I compile if the code in <script setup>, the result will be error. The following is my compiler.js file. Executing node compiler.js in CMD will compile and convert .vue files to .js files :
// compiler.js
import n_fs from 'fs';
import n_url from 'url';
import n_path from 'path';
const __filename = n_url.fileURLToPath(import.meta.url);
const __dirname = n_path.dirname(__filename);
import * as m_compiler from '@vue/compiler-sfc';
let filePath = n_path.resolve(__dirname, './Counter.vue');
let fileContent = n_fs.readFileSync(filePath, 'utf8');
let fileName = n_path.parse(filePath).name;
function transformVueSFC(source, filename){
let {descriptor, errors, } = m_compiler.parse(source, {filename:filename, });
if(errors.length !== 0){ throw new Error(errors.toString()) };
let id = Math.random().toString(36).slice(2, 12);
let hasScoped = descriptor.styles.some(function(e){ return e.scoped });
let scopeId = hasScoped ? `data-v-${id}` : undefined;
let templateOptions = {
sourceMap : false,
filename : descriptor.filename + '.vue',
id : id,
scoped : hasScoped,
slotted : descriptor.slotted,
source : descriptor.template.content,
compilerOptions : {
scopeId : hasScoped ? scopeId : undefined,
mode : 'module',
},
};
let script = m_compiler.compileScript(descriptor, {id, templateOptions, });
let template = m_compiler.compileTemplate({...templateOptions, });
let renderString = '';
let startString = script.content.includes(
'const __default__ = {') ? 'const __default__ = {' : 'export default {';
renderString = template.code.match(/export function render\(_ctx, _cache\) {([\s\S]+)/)[0];
renderString = renderString.replace('export function render(_ctx, _cache)', 'render(_ctx, _cache)');
template.code = template.code.replace(/export function render\(_ctx, _cache\) {([\s\S]+)/, '');
script.content = script.content.replace(startString, function(){
return (
startString + "\n"+
renderString +",\n" +
"__scopeId : '" + scopeId + "',\n"
+ "__file : '" + fileName + "',\n"
);
});
let insertStyles = '';
if(descriptor.styles){
let styled = descriptor.styles.map(function(style){
return m_compiler.compileStyle({id, source:style.content, scoped:style.scoped, preprocessLang:style.lang, });
});
if(styled.length){
let cssCode = styled.map(function(s){ return `${s.code}` }).join('\n');
insertStyles = (
"(function(){\n" +
"let styleTag = document.createElement('style');\n" +
"styleTag.setAttribute('data-v-"+ filename +"', '');\n" +
"styleTag.innerHTML = `"+ cssCode +"`;\n" +
"document.head.appendChild(styleTag);\n" +
'})();'
);
};
};
let jsFile01 = __dirname + '/'+ fileName + '.js';
let jsFile01Content = (insertStyles +'\n\n\n\n'+ template.code +'\n\n\n\n'+ script.content);
jsFile01Content = jsFile01Content.replace(/\n{4,}/g, '\n\n\n\n').trim();
n_fs.writeFile(jsFile01, jsFile01Content, {flag:'w', }, function(error){
if(error){ return console.error(error) };
console.log('✔ : '+ jsFile01);
});
};
transformVueSFC(fileContent, fileName);
The following is my Counter.vue file, which is in the same directory as the compiler.vue file. If it is written in the traditional way, the compilation result can run smoothly, and the counter.js file can be executed in the browser , but if it is <script setup>, the browser will report an error :
<!-- Counter.vue -->
<template>
<button type="button" @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
Here is the compiled result from Counter.js :
// Counterjs
import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
import { ref } from 'vue';
export default {
render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("button", {
type: "button",
onClick: _cache[0] || (_cache[0] = $event => (_ctx.count++))
}, _toDisplayString(_ctx.count), 1 /* TEXT */))
},
__scopeId : 'undefined',
__file : 'Counter',
setup(__props, { expose: __expose }) {
__expose();
const count = ref(0);
const __returned__ = { count, ref }
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
return __returned__
}
}
This is the compiled Counter.js file that I am using in html, DevTool will report an error, showing vue.esm-browser.js:1513 [Vue warn]: Property "count" was accessed during render but is not defined on instance. and vue.esm-browser.js:1513 [Vue warn]: Cannot mutate <script setup> binding "count" from Options API. :
<!-- html use Counter.js -->
<script async src="https://cdn.jsdelivr.net/npm/[email protected]/dist/es-module-shims.min.js"></script>
<script type="importmap">{ "imports":{ "vue":"https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js" } }</script>
<div id="app01">
<Counter></Counter>
</div>
<script type="module">
import * as Vue from 'vue';
import Counter from './Counter.js';
let vc01 = Vue.createApp({
components : {
'Counter' : Counter,
},
});
let vi01 = vc01.mount('#app01');
</script>
I've been struggling with this compilation problem for a long time, trying to figure it out myself, but failing miserably, I really hope someone can help me, thanks.
Later, I referred to Vue's official demo, and found that under the same file content, the compiled result is very similar to mine, except that its render function has more $props, $setup, $data, $options These four parameters, and it does not use _ctx.count, but uses $setup.count, the code can run after I modify it, the modified Counter.js is below, but I don't know why the compilation has these two different results.
// Counter.js
import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
import { ref } from 'vue';
export default {
render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("button", {
type: "button",
onClick: _cache[0] || (_cache[0] = $event => ($setup.count++))
}, _toDisplayString($setup.count), 1 /* TEXT */))
},
__scopeId : 'undefined',
__file : 'Counter',
setup(__props, { expose: __expose }) {
__expose();
const count = ref(0);
const __returned__ = { count, ref }
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
return __returned__
}
}