At the end of 2019, vue3.0 was officially released as an alpha version. New apis, faster speeds, and typescript support are promising. At the same time, it combines a number of advantages of hooks that make their ecology easier to migrate from frameworks like React. As a fan of React and Vue, clap! This article is inspired by the use of Vue 3.0 to do JSX(TSX) style component development, because the original Daishen did not give a demo, so we can only try to copy daishen ideas, first write a very simple babel-Plugin to achieve TSX + Vue.
Build a VuE3 + Typescript project
First we clone vue-next-webpack-Preview to a typescript project.
- the
main.js
Instead ofmain.ts
, this step only needs to change a file name extension. - new
tsconfig.json
, the basic configuration is as follows
- modified
webpack.config.js
, mainly adds pairstypescript
The processing is as follows:
{
test: /\.ts|\.tsx$/.exclude: /node_modules/.use: [
'babel-loader',
{
loader: 'ts-loader'.options: {
appendTsxSuffixTo: [/\.vue$/].transpileOnly: true}}}]For the rest, we move index.html to public, making it look like the vuecli4 project 🐶
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css'
}),
new HtmlWebpackPlugin({
title: 'vue-next-test'.template: path.join(__dirname, '/public/index.html')})],devServer: {
historyApiFallback: true.inline: true.hot: true.stats: 'minimal'.contentBase: path.join(__dirname, 'public'),
overlay: true
}
Copy the code
- for
Vue
Single file writes a declaration filesrc/globals.d.ts
That is as follows:
declare module '*.vue' {
import { Component } from 'vue'
const component: Component
export default component
}
Copy the code
- Install dependencies related to the need, by the way
[email protected]
Above, supportoption chain
Good use, thumbs up!
npm i @babel/core @babel/preset-env babel-loader ts-loader -D
npm i typescript -S
Copy the code
The directory structure of the improved project is roughly as follows
|-- .gitignore
|-- package.json
|-- babel.config.js
|-- tsconfig.json
|-- webpack.config.js
|-- plulic
|-- index.html
|-- src
|-- main.ts
|-- logo.png
|-- App.vue
|-- globals.d.ts
Copy the code
At this point, the project should still start normally. If not, please solve it yourself.
writerender
A functional component
As we all know, JSX/TSX is a syntactic sugar that will be converted to createElement/h in React and Vue, which is a key part of babel-transform-jsx’s work. To get a better idea of what JSX looks like when transcoded, let’s first write it out using Vue’s h function.
The h function in Vue3 is not quite the same as before, please refer to the render-rFC and composition-api-rFC, the main change is that it is flatter and easier to handle with Babel.
Start with a simple input. Vue, as follows
<script lang="tsx"> import { defineComponent, h, computed } from 'vue' interface InputProps { value: string, onChange? : (value: string) => void, } const Input = defineComponent({ setup(props: InputProps, { emit }) { const handleChange = (e: KeyboardEvent) => { emit('update:value', (e.target as any)! .value) } const id = computed(() => props.value + 1) return () => h('input', { class: ['test'], style: { display: 'block', }, id: id.value, onInput: handleChange, value: props.value, }) }, }) export default Input </script>Copy the code
Obviously, it’s feasible and reliable to write h directly. But it’s a hassle, and that’s why JSX is needed, first to make it easier to understand and second to improve development efficiency. But since it’s beggar, our plugin only does two things:
- Automatic injection
h
function - the
jsx
converth
function
The development ofbabel
Knowledge preparation before plug-ins
Before you start writing, please take a refresher on Babel. The following are my main references:
- Manual of the Babel plug-in
- Write a Babel plug-in from scratch
The code reference is as follows:
- plugin-transform-react-jsx
- babel-plugin-transform-vue-jsx
- online
AST parser
See the code and tutorial above to get started.
writebabel
The plug-in
Before we begin, let’s look at the AST.
Analyze this component:
- First a code block is a big one
Program
Node, we passpath
This object gets all the attributes of the node. For this simple component, we will first introduceh
Function. I’m just going to take the currentimport { defineComponent } from 'vue'
convertimport { h, defineComponent } from 'vue'
, so we can modifyProgram.body
One of the firstImportDeclaration
Node to achieve an automatic injection effect. - for
jsx
The nodes are shown as follows:
We deal with
JSXElement
Nodes can, the whole is relatively clear, theJSXElement
Nodes are replaced bycallExpression
Nodes. Now that we know the structure, let’s get started.
Automatic injectionh
function
Simply put, insert a node at the top of the code:
import { h } from 'vue'
Copy the code
So, we just need to process the Program node, we need to determine whether the code has already introduced Vue, and we need to determine whether the H function has already been introduced. The code reference is as follows:
// t 就是 babel.types
Program: {
exit(path, state) {
// Check whether Vue is introduced
const hasImportedVue = (path) = > {
return path.node.body.filter(p= > p.type === 'ImportDeclaration').some(p= > p.source.value == 'vue')}// Inject the h function
if (path.node.start === 0) {
// This is a simple judgment of the starting position, not very precise
if(! hasImportedVue(path)) {// If there is no import vue, insert a node of type importDeclaration directly
path.node.body.unshift(
t.importDeclaration(
// After the importDeclaration node is inserted, insert the ImportSpecifier node named H
[t.ImportSpecifier(t.identifier('h'), t.identifier('h'))],
t.stringLiteral('vue')))}else {
// If you already import vue, find this node and determine whether it introduces h
const vueSource = path.node.body
.filter(p= > p.type === 'ImportDeclaration')
.find(p= > p.source.value == 'vue')
const key = vueSource.specifiers.map(s= > s.imported.name)
if (key.includes('h')) {
// If it does, it doesn't matter
} else {
// Insert h into the ImportSpecifier node without import
vueSource.specifiers.unshift(t.ImportSpecifier(t.identifier('h'), t.identifier('h'))}}}}}Copy the code
conversionjsx
The Babel conversion JSX requires the replacement of nodes of type JSXElement; Replace JSXElement with callExpression, which is a function callExpression, with the following code
JSXElement: {
exit(path, state) {
/ / get JSX
const openingPath = path.get("openingElement")
const children = t.react.buildChildren(openingPath.parent)
// Only common HTML nodes are processed here for the time being. The component nodes need t.I dentifier type nodes and other nodes to be improved
const tagNode = t.stringLiteral(openingPath.node.name.name)
// Create Vue h
const createElement = t.identifier('h')
// Handle attributes
const attrs = buildAttrsCall(openingPath.node.attributes, t)
/ / create a h (tag, {... attrs}, [chidren])
const callExpr = t.callExpression(createElement, [tagNode, attrs, t.arrayExpression(children)])
path.replaceWith(t.inherits(callExpr, path.node))
}
},
Copy the code
At this point, the basic code is complete, please refer to VUE3 – TSX for complete code and engineering.
The code is limited by the author’s ability, there may be some problems, the Babel plug-in is very simple, if you have any suggestions or comments, please feel free to contact me. In reality I am a yes man, on the keyboard I punch!
I first appeared inPersonal blog