preface
The front end often interconnects with the back end. It is necessary to encapsulate a common interface request, but a common interface library is not enough. Can we use typescript D.ts files and webpack plug-ins to automate code prompting? Today I would like to share my summary of project practice.
The effect
The principle of
Read all JS files in API directory every time during hot update, parse jsDoc style comments of each interface function, and generate a corresponding D.ts description file
And began to make
Create the plugins directory
Create the plugins directory in the SRC directory and create a new API directory
Js, runner. Js, and webpack.js. Apis. Js and index.d.ts are generated by webpack plug-in.
index.js
This is the body of the request, the general API request wrapper is written here, and finally injected into the prototype of the Vue, which can call the interface using this.$API. Here is my code
import Vue from 'vue'
import axios from 'axios'
import config from '.. /.. /.. /local_env.json'
import store from '.. /.. /stores'
// Merge all interfaces into apis. Js, which is generated by the Webpack plugin
import { normalAPIs, successMessageAPIs } from './apis'
let instance = null
let api = null
Vue.use({
install(Vue, option) {
// Instantiate axios and set some general information, such as the request address
instance = axios.create({
baseURL: option.baseURL || ' '.headers: option.headers || {},
})
// Merge to interface list
constAPIs = { ... normalAPIs, ... successMessageAPIs }// Interface wrapping, passing the AXIos instance to the interface function to call the network request
const result = {}
for (const k in APIs) {
result[k] = async (data) => {
// Network requests need to catch errors
try {
const reqRes = await APIs[k](instance, data)
if (successMessageAPIs[k] && (reqRes.data.msg || reqRes.data.message)) {
store.commit('alert', {
type: 'success'.message: reqRes.data.msg || reqRes.data.message
})
}
return reqRes.data
} catch (e) {
// If an error occurs, print an error message
if (e.response) {
store.commit('alert', {
type: 'error'.message: e.response.data.msg || e.response.data.message
})
throw e
} else { // Other errors in the code
store.commit('alert', {
type: 'error'.message: e.message
})
throw new Error(e.message)
}
}
}
}
// Inject a prototype into the Vue. In the Vue instance the interface can be called through the this.$API
api = result
Vue.prototype.$api = result
}
}, {
baseURL: config.api,
headers: config.headers
})
export default api
Copy the code
Here I limit the interface function to a function that takes two arguments, the first being an instance of AXIos and the second being the argument to be sent to the interface, so that I can write the interface function in a uniform format. There are a few other things that are done here. The part of the code that calls store.mit is to send a notification to the interface that has the prompt, and to send the message that is returned in the background directly toa global toast or something like that to pop up the prompt. So we put interfaces into normalAPI and successMessageAPI objects accordingly
runner.js
This file generates apis. Js when the file changes by combining normalAPIs and successMessageAPIs into a single object. Decompress the description, parameters, and return values of each interface function into the d.TS declare Interface section, as shown below
const fs = require('fs')
const path = require('path')
/** * Merge the interfaces from each API file into a single file */
function mergeApis () {
let list = fs.readdirSync(path.resolve(__dirname, '.. /.. /api')).filter((f) = > {
returnf ! ='index.js' && f.endsWith('.js')})let apis = list.map((f) = > {
let objName = path.basename(f).split(path.extname(f))[0]
let filename = f
return {
objName,
filename,
importStr: `import ${objName}from '.. /.. /api/${filename}'`}})let filecontent = `
${apis.map(({importStr}) => {
return importStr
}).join('\n')}
const normalAPIs = {
${apis.map((api) => {
return `...${api.objName}.normalAPIs`
}).join(', ')}
}
const successMessageAPIs = {
${apis.map((api) => {
return `...${api.objName}.successMessageAPIs`
}).join(', ')}
}
export {
normalAPIs,
successMessageAPIs
}
`
fs.writeFileSync(path.resolve(__dirname, './apis.js'), filecontent)
return filecontent
}
/** * Update API index.d.ts file */
function updateApiTypeList() {
let list = fs.readdirSync(path.resolve(__dirname, '.. /.. /api')).filter((f) = > {
returnf ! ='index.js'
})
let finalFuncs = []
let interfaces = []
for(let j of list) {
// console.log(j)
// js file processing
if (j.endsWith('.js')) {
finalFuncs = finalFuncs.concat(handleJsFile(j))
}
// d.ts file processing
if (j.endsWith('.d.ts')) {
interfaces.push(handleDesTypeFile(j))
}
}
let filecontent = `
declare interface IApi {
${finalFuncs.join(',\n')}
}
${interfaces.join('\n')}
declare module 'vue/types/vue' {
interface Vue {
$api: IApi
}
}
declare var api: IApi
export default api
`
fs.writeFileSync(path.resolve(__dirname, './index.d.ts'), filecontent)
}
function handleJsFile (filename) {
// Read the file
let module = fs.readFileSync(path.resolve(__dirname, `.. /.. /api/${filename}`), 'utf-8')
/ / comment import
module = module.replace(/import(\s+)/g.'/ /')
// Remove the ES6 module
let res = module.match(/export(\s+)default(\s+){([\sa-zA-Z0-9,]+)}/)
module = module.split(res[0])
// The wrapping code is a self-calling function that returns an interface object
let moduleObj = eval(` (() = > {The ${module[0]}
let result = Object.assign(normalAPIs, successMessageAPIs)
return result
})()`)
let funcs = Object.values(moduleObj)
const finalFuncs = []
for(let f of funcs) {
// console.log(f.name, '------------------')
// Get the re for the comment
let regStr = new RegExp(` (/ \ \] [^ \ \ * * * * (* *] [^ \ \ \ \ +) + \ \ *)? \\/[^\\r\\n]*)(\\s+)(const|let)(\\s+)${f.name} `)
// Get comments
let comment = (module[0].match(regStr) || [])[0] | |' '
if (comment.startsWith('} ')) {
comment = comment.slice(1)}if (comment.endsWith(`${f.name} `)) {
let endReg = new RegExp(`(const|let)(\\s)+${f.name} `)
comment = comment.replace(endReg, ' ')}// Get parameters. There is no support for writing parameters with deconstruction
let define = (f.toString().match(/\([a-zA-z\d,\s]+\)(\s+)=>(\s+){/) | | []) [0]
if(! define)continue
define = define.replace(/\)(\s+)=>(\s+){/.' ')
define = define.replace(/\((\s*)rq(\s*)([,]*)/.' ')
// The parameter type is obtained from the comment
let defineType = ' '
// Parameter annotation regex
const paramCommentReg = new RegExp(`@param \{([A-Za-z0-9\\[\\]<>]+)\} ${define.trim()}`)
if (define) {
const match = comment.match(paramCommentReg)
if (match) {
defineType = match[1]}}// Get the result type
let returnType = 'any'
const returnCommentReg = new RegExp(`@return \{([A-za-z0-9\\[\\]<>]+)\}`)
const returnMatch = comment.match(returnCommentReg)
// console.log(f.name ,comment, returnMatch)
if (returnMatch) {
returnType = returnMatch[1]}// console.log(returnType)
finalFuncs.push(`${comment}${f.name}(${define.trim()}${defineType ? ` :${defineType}` : ' '}): Promise<${returnType}> `)}return finalFuncs
}
function handleDesTypeFile (filename) {
const str = fs.readFileSync(path.resolve(__dirname, `.. /.. /api/${filename}`), 'utf-8')
return str
}
module.exports = {updateApiTypeList, mergeApis}
Copy the code
webpack.js
This file is the definition part of the WebPack plug-in, and the code is as follows
const {updateApiTypeList, mergeApis} = require('./runner')
function AutoApiPlugin(options) {}
AutoApiPlugin.prototype.apply = function(compiler) {
let filelist = mergeApis()
compiler.plugin('emit'.function(compilation, callback) {
try {
updateApiTypeList()
} finally {
callback()
}
})
}
module.exports = AutoApiPlugin
Copy the code
If a file is added or deleted, you need to restart the project. The correct way to write this is to retrieve the file list at each change and then perform the update
To register the plugin
Introduce the API in main.js
import './plugins/api'
Copy the code
Add the WebPack plug-in to vue.config.js
const AutoApiPlugin = require('./src/plugins/api/webpack')
module.exports = {
configureWebpack: (config) = > {
config.plugins.push(
new AutoApiPlugin({})
)
}
}
Copy the code
Try writing an interface
Create a new demo.js file in the SRC/API directory
/ * * * *@param {*} rq AxiosInstance
* @param {IProduct} Data Format for adding materials *@return {IReturn}* /
const CreateProduct = async (rq, data) => {
let{ files=[], ... rest } = data files = files.map((file) = > {
return {
file_url: file.url,
name: file.name
}
})
constpostData = { files, ... rest }let res = await rq.post('product/create', postData)
return res
}
Copy the code
Create a demo.d.ts in the same directory as the code below
interface IProduct {
name: string.code: string.remark: string
}
interface IReturn {
message: string.data: IProduct
}
Copy the code
Run the projectnpm run serve
After the plug-in is executed, theplugins/api
Directory generationapis.js
andindex.d.ts
Files in the editor can also see the code prompt
If you don’t see the code prompt the first time you run it, restart the project and start again.
conclusion
Through such an experiment, I tried to write a simple Webpack plug-in by myself, and standardized the writing of interface files through D. TS. In the future, I can combine Swagger or other plug-ins to complete more work of simplifying interface writing