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.

  • themain.jsInstead ofmain.ts, this step only needs to change a file name extension.
  • newtsconfig.json, the basic configuration is as follows
  • modifiedwebpack.config.js, mainly adds pairstypescriptThe 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
  • forVueSingle file writes a declaration filesrc/globals.d.tsThat 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 chainGood 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.

writerenderA 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 injectionhfunction
  • thejsxconverthfunction

The development ofbabelKnowledge 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
  • onlineAST parser

See the code and tutorial above to get started.

writebabelThe plug-in

Before we begin, let’s look at the AST.

Analyze this component:

  • First a code block is a big oneProgramNode, we passpathThis object gets all the attributes of the node. For this simple component, we will first introducehFunction. I’m just going to take the currentimport { defineComponent } from 'vue'convertimport { h, defineComponent } from 'vue', so we can modifyProgram.bodyOne of the firstImportDeclarationNode to achieve an automatic injection effect.
  • forjsxThe nodes are shown as follows:

    We deal withJSXElementNodes can, the whole is relatively clear, theJSXElementNodes are replaced bycallExpressionNodes. Now that we know the structure, let’s get started.

Automatic injectionhfunction

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