-
-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
latest progress #58
Comments
Hi @ErKeLost, curious as to why you ping Jafar as he isn't a part of the project. To answer your question, I am actively working on making Fervid fully compliant with the official specification. You can see progress on the umbrella issue: #16 If you want to contribute, there are two untouched opportunities at the moment:
Please tell me if you're interested in any of the options above or maybe would like to contribute somewhere else? :) |
I want to first try to build plugins that are completely consistent with I now have a question about 'vue/compiler-sfc' if we ignore the ssr issue for the time being, can the entire behavior support me to complete the unplugin-vue-fervid project? I also have another idea if we can ultimately maintain the same behavior as vue/compiler-sfc, then we can finally discuss merging unplugin-vue-fervid into unplugin-vue,(this requires completing ssr work) The ultimate goal is to implement a pure native @farmfe/plugin-vue plug-in so as to ensure 100% performance output I am really interested in vue Ecology recording an issue under unplugin-vue |
My idea is that if the basic project |
Yes, the CSR code output is almost identical with some minor discrepancies which I am actively trying to cover. It is generally very usable already (although needs "highly experimental" note).
I am afraid it is technically impossible to maintain 1-to-1 compatibility without severely crippling the native speed of Fervid. The reason being - JS compiler is lousy in that it uses JS plugins, functions, etc:
Fervid intends to be the real parallel compiler (with speed of up to 60K files/sec), and calling JS destroys that dream
I agree here, I think having a native plugin is the ultimate goal :)
What do you mean by "frameworks"?
I see Farm initially providing a plugin for CSR and later supporting SSR as well |
|
I agree with your points. Is there anything else or more specific you have questions about? |
Not yet. I want to start this project. If I have any questions, I will always ask you. |
hi @phoenix-ru this is my current understanding of the vue compilation process i will first summarize it as a whole and then proceed to the point of compiling the specific core parts of the code i will only describe the vue compilation process and the current difference between fervid and compiler-sfc, the next goal is to get as close to compiler-sfc functionality as possible i choose vite-plugin-vue as the segmentation point among vue-loader and vite-plugin-vue to be more in line with the modern plugin process plugin compilation process
load(id) {
// 1. handle helper code
if (id === EXPORT_HELPER_ID) {
return helperCode;
}
} helper codeexport const EXPORT_HELPER_ID = "\0/plugin-vue/export-helper";
export const helperCode = `
export default (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
}
`;
Traverse the props array, injecting each attribute into the component options These attributes typically include:
The role of this helper is to ensure: Correct attribute merge: Ensure that all compile-time attributes are correctly merged into the component transform codesuch as vite bundler, We need to distinguish between the development environment and the production environment. For example, vite optimizes the development environment dev server and does not split multiple module requests, that is, it does not split the entire vue file into multiple js modules and return the esm http request. There are three operations in transform:
Triggered when a. vue file is requested directly, the first compilation is triggered
if (!query.vue) {
return transformMain(
code,
filename,
options.value,
context,
ssr,
customElementFilter.value(filename),
);
} Main responsibilities:
export async function transformMain(code, filename, options, ...) {
// 1. create descriptor
const { descriptor, errors } = createDescriptor(filename, code, options);
// 2. handle script
const { code: scriptCode, map: scriptMap } = await genScriptCode(
descriptor,
options,
pluginContext,
ssr,
customElement,
);
// 3. handle template
let templateCode = '';
if (hasTemplateImport) {
({ code: templateCode } = await genTemplateCode(
descriptor,
options,
pluginContext,
ssr,
customElement,
));
}
// 4. handle styles
const stylesCode = await genStyleCode(
descriptor,
pluginContext,
customElement,
attachedProps,
);
// 5. handler custom blocks
const customBlocksCode = await genCustomBlockCode(descriptor, pluginContext);
// merge all compilation code
const output: string[] = [
scriptCode,
templateCode,
stylesCode,
customBlocksCode,
];
}
// 1. add components props scopedId
if (hasScoped) {
attachedProps.push([`__scopeId`, JSON.stringify(`data-v-${descriptor.id}`)]);
}
// 2. add file info (for dev tools)
if (devToolsEnabled || (devServer && !isProduction)) {
attachedProps.push([
`__file`,
JSON.stringify(isProduction ? path.basename(filename) : filename),
]);
}
// 3. use helper to merge all properties (helper will merge all properties into component)
output.push(
`export default /*#__PURE__*/ _export_helper(_sfc_main, [
${attachedProps.map(([key, val]) => `['${key}',${val}]`).join(",\n ")}
])`,
);
// 1. check if HMR is enabled
if (
devServer &&
devServer.config.server.hmr !== false &&
!ssr &&
!isProduction
) {
// 2. add HMR ID
output.push(
`_sfc_main.__hmrId = ${JSON.stringify(descriptor.id)}`,
// 3. create HMR record
`typeof __VUE_HMR_RUNTIME__ !== 'undefined' && ` +
`__VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)`,
// 4. listen to file changes
`import.meta.hot.on('file-changed', ({ file }) => {
__VUE_HMR_RUNTIME__.CHANGED_FILE = file
})`,
);
// 5. check if only template has changed
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
output.push(
`export const _rerender_only = __VUE_HMR_RUNTIME__.CHANGED_FILE === ${JSON.stringify(
normalizePath(filename),
)}`,
);
}
// 6. add HMR accept handling
output.push(
`import.meta.hot.accept(mod => {
if (!mod) return
const { default: updated, _rerender_only } = mod
if (_rerender_only) {
// only template changed, just rerender
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
// other changes, reload the entire component
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})`,
);
}
The above is the logic of all transform main. In the first stage, we can actually complete all hmr and vue compilation functions in the development environment The main code logic we generated is This is the most important code in the first phase of dev mode
"import { defineComponent as _defineComponent } from 'vue'\n" +
'import { ref } from "vue";\n' +
'\n' +
'const _sfc_main = /*@__PURE__*/_defineComponent({\n' +
" __name: 'App',\n" +
' setup(__props, { expose: __expose }) {\n' +
' __expose();\n' +
'\n' +
'const msg = ref("22222222");\n' +
'console.log(123132);\n' +
'\n' +
'const __returned__ = { msg }\n' +
"Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n" +
'return __returned__\n' +
'}\n' +
'\n' +
'})',
'import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, vModelText as _vModelText, withDirectives as _withDirectives, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"\n' +
"\n" +
"function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {\n" +
' return (_openBlock(), _createElementBlock("div", null, [\n' +
' _cache[1] || (_cache[1] = _createTextVNode(" 123132 ")),\n' +
' _cache[2] || (_cache[2] = _createElementVNode("div", null, "4565465", -1 /* HOISTED */)),\n' +
' _cache[3] || (_cache[3] = _createElementVNode("h1", null, "Hello world", -1 /* HOISTED */)),\n' +
' _cache[4] || (_cache[4] = _createTextVNode(" 3123132ww ")),\n' +
' _createElementVNode("h2", null, _toDisplayString($setup.msg), 1 /* TEXT */),\n' +
' _cache[5] || (_cache[5] = _createTextVNode(" 12313211231222146521312wwwwww321 ")),\n' +
' _withDirectives(_createElementVNode("input", {\n' +
' "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($setup.msg) = $event)),\n' +
' type: "text"\n' +
" }, null, 512 /* NEED_PATCH */), [\n" +
" [_vModelText, $setup.msg]\n" +
" ])\n" +
" ]))\n" +
"}",
"\n";
'import "/Users//examples/vite/src/App.vue?vue&type=style&index=0&lang.css"',
'_sfc_main.__hmrId = "7a7a37b1"',
"typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)",
"import.meta.hot.on('file-changed', ({ file }) => {",
" __VUE_HMR_RUNTIME__.CHANGED_FILE = file",
"})",
"import.meta.hot.accept(mod => {",
" if (!mod) return",
" const { default: updated, _rerender_only } = mod",
" if (_rerender_only) {",
" __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)",
" } else {",
" __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)",
" }",
"})";
import _export_sfc from '\x00/plugin-vue/export-helper'",
`export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"/Users//examples/vite/src/App.vue"]])` Running this code, we complete the compilation of vue. This is the complete first stage of the process. Next we start to describe the details First function point descriptor
const { descriptor, errors } = createDescriptor(filename, code, options);
interface SFCDescriptor {
// file path
filename: string;
// source code
source: string;
// unique identifier
id: string;
// information of each block
template: SFCTemplateBlock | null;
script: SFCScriptBlock | null;
scriptSetup: SFCScriptBlock | null;
styles: SFCStyleBlock[];
customBlocks: SFCBlock[];
// CSS variables
cssVars: string[];
// Whether it contains the Slotted API
slotted: boolean;
} interface SFCTemplateBlock extends SFCBlock {
type: "template";
content: string;
lang?: string; // such as 'pug'
ast?: any; // template AST
src?: string; // external template path
} interface SFCScriptBlock extends SFCBlock {
type: "script";
content: string;
lang?: string; // such as 'ts'
src?: string; // external script path
setup?: boolean; // whether it's a setup script
bindings?: BindingMetadata; // variable bindings
} interface SFCStyleBlock extends SFCBlock {
type: "style";
content: string;
lang?: string; // such as 'scss'
scoped?: boolean; // whether it's scoped style
module?: string | boolean; // CSS Modules configuration
}
// handler script info
const scriptBindings = descriptor.scriptSetup?.bindings;
// handler scoped id check if has scoped
const hasScoped = descriptor.styles.some((s) => s.scoped);
// diff old descriptor and new descriptor to detect if only template has changed
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
// update template only
}
// Example: CSS variables are passed from style to template
const cssVars = descriptor.cssVars; Importance of Descriptors:
There are also a few more important methods in the compilation of vueI was thinking about distinguishing these methods and code logic in fervid. Now that fervid only provides one method, can we achieve consistent behavior with compiler-sfc? I am thinking that without considering caching and ssr in the first step, we need to implement the following method
|
Hi @ErKeLost, I've read through your research and it looks mostly correct. Fervid also significantly differs in a way it compiles the code. Instead of doing string concatenation like the official compiler, Fervid fully operates with IR+AST. It provides some guarantees about the processing and the output. The official compiler is also moving towards AST, but very slowly and inconsistently. As to assembling the parts of SFC into a single file (which you called "optimization"), Fervid does it out-of-the-box and much smarter. Since it works with AST directly, it merges properties directly into the object and thus no helpers/additional things are needed. The only thing Fervid does not handle by design is HMR (because it differs across bundlers). This will be up to the plugin. So, in short — there will be no descriptor in the JS side and any manipulations should happen in the Rust side. |
Has fervid already done all the operations on the rust side? For example, the problem of attribute merging incorporates some functions like scopedId? I haven't checked the specific code of fervid yet. I will check the code logic in the near future. |
Yep. Most of Rust transforms are implemented and I am polishing them at the moment so that it matches the official compiler as much as possible. |
my idea is whether we can synchronize compiler-sfc methds as much as possible and expose them through napi, which will allow us to better optimize the code and synchronize the code in the future, regardless of compiler-sfc updates or we can eventually replace compiler-sfc. Of course, we can support the current effect of compile. I wonder if we can split it more carefully and release different js methods for different functions. |
Would this mean exposing the descriptor somehow? If you want to go this route, we can certainly do it using You would need to provide methods which operate on an Another question - how much freedom in JS-land do you want to have? You wouldn't be able to operate on a descriptor except for calling Napi functions exported by Fervid. A good place for you to start would be fervid/crates/fervid/src/lib.rs Lines 131 to 224 in 6c7047e
|
What I want to do is try to keep the api consistent. The descriptor only corresponds to the behavior of compiler-sfc. I don't seem to need to change the descriptor at the moment. I don't know if there are still some deviations in my understanding |
hi, @JafarAkhondali is there any latest progress now? I'm ready to continue developing
unplugin-vue-fervid
and@farmfe/plugin-vue
The text was updated successfully, but these errors were encountered: