Are you going to write a simple API Mock server these days? I was actually trying to talk about JSX, the Mock server was just a cover.
I’m looking for a more concise, convenient, flexible and extensible way to define Mock apis. Later, I discovered the advantages and potential of JSX in domain problem description. Of course, this is not empty talk, we will actually write a project to confirm this judgment.
The article Outlines
- 1. Description of domain problems
- 1.1 Configuration file Format
- 1.2 Programming languages and internal DSL
- 2. JavaScript internal DSL
- 2.1 Object Form
- 2.2 Chain call form
- 2.3 ES2015 Template Tag
- 2.4 How about JSX?
- 3. The JSX primer
- 3.1 Custom factories
- 3.2 Host Component vs Custom Component
- 3.3 Simply implement the createElement factory method
- 4. Design of basic components
- 4.1 Inspiration from Koa
- 4.2 Use Basic Components
- 4.3 Encapsulation of high-level Components
- 5. Talk about the implementation principle
- 5.1 ‘Render’
- 5.2 run
- 6. Conclusion, finally done
- 7. Extension
1. Description of domain problems
What the hell is a domain problem? What is a field? It is very well explained on the Wiki. A field is the area covered by a particular profession or subject. Domain problems can then be understood as requirements that we need to solve programmatically or otherwise.
When it comes to API Mock servers, for example, we need to solve the problem of request matching and data emulation; Nginx addresses resource servos and proxies; HTML + CSS solves the problem of page UI presentation…
We focus here on ‘description’. These descriptions are the ‘front end’ or user interface (UI) provided to the domain expert. Here’s an example:
Descriptions can take many forms, such as configuration files, programming languages, graphical interfaces. Let’s take a look at how common tools now work:
1.1 Configuration file Format
JSON?
JSON is a very simple representation of data that has no learning cost and is very easy to parse. However, it has a number of fatal drawbacks, such as no support for annotations, redundancy, and a single data structure.
YAML?
The syntax is much cleaner and more readable than JSON. The form is excellent as a configuration file
Or some other configuration file…
Typically these profiles are language-independent and therefore do not contain language-specific elements. In other words, configuration file data is relatively static, so it is not flexible and extensible. Only suitable for simple configuration scenarios.
For example, these configuration files do not support functions. Our Mock server probably needs a function to handle requests dynamically, so configuration files are not appropriate here.
Of course you can replace ‘functions’ in other ways, such as templates or script support
1.2 Programming languages and internal DSL
We need to go back to the programming language itself and take advantage of its programming capabilities to achieve more power than configuration files can.
But with general-type programming languages alone, imperative procedure descriptions can be too cumbersome. It is best to simplify and abstract domain-specific problems and provide users with a friendly user interface that allows them to describe their domain-specific problems declaratively. We want to minimize the user’s dependence on low-level details, while at the same time maintaining flexible scalability.
I’m probably talking about Domain-specific languages (DSLS):
A DSL is a computer language used to describe a specific application domain. DSL has a wide range of applications in the computer field, such as HTML to describe Web pages, DATABASE query language SQL, regular expressions. The equivalent is GPL (general-purpose Language), such as Java, C++, and JavaScript. They can be used to describe arbitrary domain logic, and they are usually Turing-complete. One way to think about it, though loosely: all but generic type languages are DSLS.
How do I create a DSL?
Developing a new language from scratch? No! The cost is too high
A more elegant approach is to subtract or encapsulate abstractions from a general-purpose programming language. Of course, not all typologies have this’ capability ‘, such as Java or C/C++, where the syntax is too Verbose or the toolchain is too heavy. But Languages like Groovy, Ruby, Scala, and Elixir can easily create ‘DSLS,’ and most of them are dynamic.
Some of them have macros, some have natural syntax for DSLS, some have very strong dynamic programming capabilities… These factors make them suitable hosts for DSLS.
We also commonly refer to such DSLS as Embedded DSLS or internal DSLS because they are parasitic in general-type programming languages. Independent DSLS, such as JSON and HTML, are called external DSLS.
The internal DSL has the advantage of eliminating the complexity of implementing a language (Parse->Transform->Generate).
Two very typical examples:
Gradle, common for Java developers, based on Groovy:
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
api 'org.apache.com mons: Commons - math3:3.6.1 track'
implementation : 'com. Google. Guava guava: 27.0.1 - jre'
testImplementation 'junit: junit: 4.12'
}
Copy the code
There’s also CocoaPods, based on Ruby:
source 'http://source.git'
platform :ios.'8.0'
target 'Demo' do
pod 'AFNetworking'
pod 'SDWebImage'
pod 'Masonry'
pod "Typeset"
pod 'BlocksKit'
pod 'Mantle'
pod 'IQKeyboardManager'
pod 'IQDropDownTextField'
end
Copy the code
Implementation details are beyond the scope of this article, but back to JavaScript.
I personally want DSLS to have these features:
- Focus on specific areas. That is, its purpose is very clear and therefore much simpler than a general-type language, but its boundaries are sometimes tricky to grasp.
- Organized. It should be easy to organize and describe domain problems, or it should be a constraint capability. Configuration files are very well organized, such as JSON, which can easily describe data structures without any mental burden. Another classic example is unit testing frameworks (such as JEST) that use components like Describe, IT, and Expect to make unit tests better organized.
- Readability. It must be human readable and easy to understand.
- Declarative. Declarative over procedural, describing What rather than How.
- Extensibility. Many DSLS don’t focus on this at first because the problem may not be complicated at first. The area of concern is not static, it can grow, and the extensibility of a DSL becomes critical. In the case of HTML, as front-end development becomes more complex, the original set of elements and functions can no longer meet the requirements, so many components or custom element schemes are derived. If the existing DSL cannot be extended, a new DSL layer can be built on top of it, such as CSS vs SASS and HTML vs React.
2. JavaScript internal DSL
The fact that Groovy and Ruby are ‘suitable’ as DSLS mentioned in the previous section doesn’t necessarily mean that they should be implemented, it just means that there are some language features that come naturally to them that make implementation easier, or look cleaner.
Google has very little available information on a ‘JavaScript DSL’ match. If you feel confused, you should go back to the problem itself. The most important thing is to solve the domain problem, and how to organize and describe it is relatively secondary. So don’t worry about whether JavaScript is suitable or not.
Let’s take a look at the typical organization of JavaScript internal DSLS for the specific domain of Mock Server:
2.1 Object Form
The simplest way is to declare directly based on objects or arrays, which is simple but still organized. Umi Mock and Ice Mock, for example, are organized based on objects:
export default {
// The value can be Object or Array
'GET /api/users': { users: [1.2]},// GET POST can be omitted
'/api/users/1': { id: 1 },
// Support custom functions, API see express@4
'POST /api/users/create': (req, res) = > {
res.end('OK')},// Use third-party libraries such as Mockjs
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city'.'value|1-100': 50.'type|0-2': 1}],})}Copy the code
Like configuration files, simple API Mock scenarios are easy to implement and use, and complex usage and API protocols can be further encapsulated by custom functions. But sometimes we wish libraries could take on a little more.
2.2 Chain call form
Another typical form of JavaScript as an internal DSL is the chain call.
The best known of these is JQuery, which popularized the chain invocation pattern. Compared to verbose native DOM manipulation code, JQuery really shines. It exposes a streamlined API that hides many of the low-level DOM manipulation details and smooths out platform differences while maintaining flexibility and extensibility. That’s why it’s really popular. People love simple things.
$('.awesome')
.addClass('flash')
.draggable()
.css('color'.'red')
Copy the code
JQuery’s API pattern has also influenced other areas, such as Ruff in Iot:
$.ready(function(error) {
if (error) {
console.log(error)
return
}
/ / the lights
$('#led-r').turnOn()
})
Copy the code
jest
expect(z).not.toBeNull()
expect(z).toBeDefined()
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
Copy the code
There are also two examples in the API Mock server world:
Nock:
const scope = nock('http://myapp.iriscouch.com')
.get('/users/1')
.reply(404)
.post('/users', {
username: 'pgte'.email: '[email protected]',
})
.reply(201, {
ok: true.id: '123ABC'.rev: '946B7D1C',
})
.get('/users/123ABC')
.reply(200, {
_id: '123ABC'._rev: '946B7D1C'.username: 'pgte'.email: '[email protected]',})Copy the code
And the Srvx of the netease Cloud team
get('/handle(.*)').to.handle(ctx= > {
ctx.body = 'handle'
})
get('/blog(.*)').to.json({ code: 200 })
get('/code(.*)').to.send('code'.201)
get('/json(.*)').to.send({ json: true })
get('/text(.*)').to.send('haha')
get('/html(.*)').to.send('<html>haha</html>')
get('/rewrite:path(.*)').to.rewrite('/query{path}')
get('/redirect:path(.*)').to.redirect('localhost:9002/proxy{path}')
get('/api(.*)').to.proxy('http://mock.server.com/')
get('/test(.*)').to.proxy('http://mock.server.com/', {
secure: false,
})
get('/test/:id').to.proxy('http://{id}.dynamic.server.com/')
get('/query(.*)').to.handle(ctx= > {
ctx.body = ctx.query
})
get('/header(.*)')
.to.header({ 'X-From': 'svrx' })
.json({ user: 'svrx' })
get('/user').to.json({ user: 'svrx' })
get('/sendFile/:path(.*)').to.sendFile('./{path}')
Copy the code
The chained invocation pattern is currently the dominant form of JavaScript internal DSL. It is also relatively simple to implement and, more importantly, it is close to natural language.
2.3 ES2015 Template Tag
Libraries that introduce ‘new languages’ to JavaScript based on the ES6 Template Tag feature have emerged in recent years.
But because the ES6 Template Tag is essentially a string, it needs to be parsed and transformed, so it’s more like an external DSL. Don’t forget Compiler as Framework! It is often possible to use the Babel plug-in to convert them into JavaScript code ahead of time at compile time.
To name a few popular examples:
Zebu: This is a small compiler dedicated to parsing Template tags. Take a look at some of its built-in examples:
/ / range
range` 1, 3... ` (10) // [1, 3, 5, 7, 9]
// State machine
const traffic = machine`
initState: #green
states: #green | #yellow | #red
events: #timer
onTransition: ${state => console.log(state)}
#green @ #timer -> #yellow
#yellow @ #timer -> #red
#red @ #timer -> #green
`
traffic.start() // log { type: "green" }
traffic.send({ type: 'timer' }) // log { type: "yellow" }
Copy the code
Jest table test:
describe.each`
a | b | expected
The ${1} | The ${1} | The ${2}
The ${1} | The ${2} | The ${3}
The ${2} | The ${1} | The ${3}
`('$a + $b'.({ a, b, expected }) = > {
test(`returns ${expected}`.() = > {
expect(a + b).toBe(expected)
})
test(`returned value not be greater than ${expected}`.() = > {
expect(a + b).not.toBeGreaterThan(expected)
})
test(`returned value not be less than ${expected}`.() = > {
expect(a + b).not.toBeLessThan(expected)
})
})
Copy the code
Among other things:
- htm
- graphql-tag
- styled-components
We had a lot of imagination with Template Tag. However, it does introduce some complexity. As mentioned at the beginning, they are strings that require parsing, syntax checking, and conversion, and JavaScript’s own language mechanics don’t make them very convenient (syntax highlighting, type checking).
2.4 How about JSX?
So much setup, just foreplay. The solutions mentioned above are either too simple, too complex or plain. I set my sights on JSX and found that it could meet most of my needs.
Let’s take a look at our Mock server prototype:
import { Get, Post, mock } from 'jsxmock'
export default (
<server port="4321">{/ * * home page /}<Get>hello world</Get>Login {/ * * /}<Post path="/login">login success</Post>{/* Return JSON */}<Get path="/json">{{ id: 1 }}</Get>
{/* mockjs */}
<Get path="/mockjs">{mock({ 'id|+1': 1, name: '@name' })}</Get>{/* Custom logic */}<Get path="/user/:id">{(req, res) => res.send('hello')}</Get>
</server>
)
Copy the code
Nested matching scenarios
export default (
<server>
<Get path="/api">{/ * matching/API? method=foo */}<MatchBySearch key="method" value="foo">
foo
</MatchBySearch>{/ * matching/API? method=bar */}<MatchBySearch key="method" value="bar">
bar
</MatchBySearch>
<BlackHole>I will eat any request</BlackHole>
</Get>
</server>
)
Copy the code
A bit Verbose? Further encapsulate components:
const MyAwesomeAPI = props= > {
const { path = '/api', children } = props
return (
<Get path={path}>
{Object.keys(children).map(name => (
<MatchBySearch key="method" value={name}>
{children[name]}
</MatchBySearch>
))}
</Get>)}export default (
<server>
<MyAwesomeAPI>{{ foo: 'foo', bar: 'bar' }}</MyAwesomeAPI>
<MyAwesomeAPI path="/api-2">{{ hello: 'foo', world: 'bar' }}</MyAwesomeAPI>
</server>
)
Copy the code
Looks good, huh? We saw the potential of JSX as a DSL and moved the React component thinking beyond the GUI.
You know my style, it’s long ☕️ take a break and read on.
3. The JSX primer
If you’re a React developer, JSX should be all too familiar. It’s just a syntactic sugar, but it’s not currently part of the JavaScript standard. Both Babel and Typescript support JSX translation.
For example,
const jsx = (
<div foo="bar">
<span>1</span>
<span>2</span>
<Custom>custom element</Custom>
</div>
)
Copy the code
Will be translated as:
const jsx = React.createElement(
'div',
{
foo: 'bar',
},
React.createElement('span'.null.'1'),
React.createElement('span'.null.'2'),
React.createElement(Custom, null.'custom element'))Copy the code
3.1 Custom factories
JSX requires a factory method to create a ‘node instance’. The default is react.createElement. We can comment the configuration to prompt the translation plug-in. Custom factories are named H by convention:
/* @jsx h */
/* @jsxFrag 'fragment' */
import { h } from 'somelib'
const jsx = (
<div foo="bar">
<span>1</span>
<span>2</span>
<>fragement</>
</div>
)
Copy the code
Translate:
import { h } from 'somelib'
const jsx = h(
'div',
{
foo: 'bar',
},
h('span'.null.'1'),
h('span'.null.'2'),
h('fragment'.null.'fragement'))Copy the code
3.2 Host Component vs Custom Component
JSX distinguishes between two component types. Lower-case components are built-in components that are passed createElement as a string. Uppercase indicates a custom component. This variable must exist in scope or an error will be reported.
// Built-in components
;<div />
// Custom components
;<Custom />
Copy the code
3.3 Simply implement the createElement factory method
export function createElement(type, props, ... children) {
constcopy = { ... (props || EMPTY_OBJECT) } copy.children = copy.children || (children.length >1 ? children : children[0])
return {
_vnode: true,
type,
props: copy,
}
}
Copy the code
4. Design of basic components
4.1 Inspiration from Koa
You should be familiar with the KOA middleware mechanism.
// logger
app.use(async (ctx, next) => {
await next()
const rt = ctx.response.get('X-Response-Time')
console.log(`${ctx.method} ${ctx.url} - ${rt}`)})// x-response-time
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time'.`${ms}ms`)})// response
app.use(async ctx => {
ctx.body = 'Hello World'
})
Copy the code
Figuratively speaking, it is an onion model:
The middleware calls Next and goes to the next level. If I break the boundary of the function. It does look like an onion:
✨ I found it more intuitive to represent this onion structure using JSX
4.2 Use Basic Components
Hence the basic component
. It is similar to Koa’s app.use, which intercepts requests and can either respond or choose to go to the next level.
① Take a look at the overall design.
Use builds on the above, using JSX to describe the underlying components of the middleware package hierarchy. Since we are using a tree structure, we need to distinguish between sibling middleware and child middleware:
<server>
<use m={A}>
<use m={Aa} />
<use m={Ab} />
</use>
<use m={B} />
<use m={C} />
</server>
Copy the code
Aa and Ab are the child middleware of A. The KOA-like next function can be called from A to access the lower-level middleware.
A, B, and C are sibling middleware. When the current successor middleware does not match, the next adjacent middleware is executed.
At first glance, this is a combination of KOA and Express!
② Look at the Props design
interface UseProps {
m: (req, res, recurse: () => Promise<boolean>) = > Promise<boolean>; skip? : boolean; }Copy the code
-
m
-
Req and RES: Express request objects and response objects
-
Recurse: Recursive execution child middleware, similar to KOA’s Next. Return a Promise< Boolean > that will resolve after the lower-level middleware completes execution, Boolean indicating whether the lower-level middleware matched the intercepted request.
-
Return value: Returns a Promise< Boolean > indicating whether the current middleware matches (intercepting the request). If a match is made, subsequent sibling middleware will not be executed.
-
-
Skip: Forced skip. We may temporarily skip matching requests during development, which is a bit like skip in unit testing
③ Take a look at the running example
Suppose the code is:
const cb = name= > () = > {
console.log(name)
return false
}
export default (
<server>
<use
m={async (req.res.rec) = >{console.log('A') if (req.path === '/user') await rec() console.log('end A') return false}} > {console.log('A') if (req.path === '/user') await rec()<use m={cb('A-1')} >If the parent matches, this is executed</use>
<use m={cb('A-2')} >.</use>
</use>
<use m={cb('B')} / >
<use m={cb('C')} / >
</server>
)
Copy the code
If the request is ‘/’, then print A -> end A -> B -> C; If the request is ‘/user’, then A- > A-1 -> A-2 -> end A- > B -> C is printed
Our base component, like Koa/Express, kept the core very small and concise, but of course it was low level, which allowed for flexibility.
This simple basic component design is the ‘cornerstone’ of the entire framework. If you know Koa and Express, there’s nothing new here. It’s just a different way of presenting it.
4.3 Encapsulation of high-level Components
Ok, with use as a base primitive, I can do a lot of interesting things and encapsulate higher-level apis with componentized thinking.
1.<Log>
Play the log:
Encapsulate the simplest component:
export const Log: Component = props= > {
return (
<use
m={async (req.res.rec) = >{const start = date.now () // Enter the next level const RTN = await rec() console.log(' ${req.method} ${req.path}: ${Date.now() - start}ms` ) return rtn }} > {props.children}</use>)}Copy the code
Usage:
<server>
<Log>
<Get>hello world</Get>
<Post path="/login">login sucess</Post>.</Log>
</server>
Copy the code
2.<NotFound>
: 404
export const NotFound = props= > {
const { children } = props
return (
<use
m={async (req.res.rec) = >{ const found = await rec() if (! Res.status (404) res.send('Not found ')} return true}} > {children}</use>)}Copy the code
Use the same as Log. Recurse returns false, indicating that the child did not match the request.
3.<Catch>
: Exception handling
export const Catch: Component = props= > {
return (
<use
m={async (req.res.rec) = > {
try {
return await rec()
} catch (err) {
res.status(500)
res.send(err.message)
return true
}
}}
>
{props.children}
</use>)}Copy the code
Use the same as Log. Catch exceptions in the low-level middleware.
(4)<Match>
: Request match
The Match component is also a very basic component upon which other high-level components are implemented. It matches requests and responds to them. Let’s look at Props design:
export type CustomResponder =
| MiddlewareMatcher
| MockType
| boolean
| string
| number
| object
| null
| undefined
exportinterface MatchProps { match? :(req: Request, res: Response) = > boolean // Request a matchheaders? : StringRecord// The default response headercode? : number | string// The default response code
// The children type is more complex, and can be primitive types, objects, Mock objects, custom response functions, and lower-level middlewarechildren? : ComponentChildren | CustomResponder }Copy the code
Match component body:
export const Match = (props: MatchProps) = > {
const { match, skip, children } = props
// Convert to children
let response = generateCustomResponder(children, props)
return (
<use
skip={skip}
m={async (req.res.rec) = >{// Check if it matches if (match? match(req, res) : True) {if (response) {return response(req, res, rec)} Return rec()} return false}} > {children}</use>)}Copy the code
Due to space limitations, details of Match can be found here
Forward, forward. Get, Post, Delete, MatchByJSON, and MatchBySearch are all wrapped around Match.
5.<Delay>
: Delayed response
Too excited, accidentally write too long, I can go to write a small book. Ok, as a final example, in the Mock API there is a scenario to simulate delayed responses. The implementation is simple:
export const Delay = (props: DelayProps) = > {
const { timeout = 3000. other } = propsreturn (
<use
m={async (req.res.rec) = > {
await new Promise(res => setTimeout(res, timeout))
return rec()
}}
>
<Match {. other} / >
</use>)}Copy the code
Usage:
<Get path="/delay">
{/* Delay return for 5s */}
<Delay timeout={5000}>Delay Delay... </Delay> </Get>Copy the code
See the JSXMock documentation for more use cases.)
It’s not easy to get here, and you might be interested in how it works, so keep reading.
5. Talk about the implementation principle
Take a quick look at the implementation. If you know how React or Virtual-dom works. It all makes sense.
5.1 ‘Render’
This is’ render ‘in quotes. This is just a colloquial term and does not mean that it will render into a GUI. It is used to expand the entire JSX tree. It’s easy for us, we don’t have anything to do with updates or UI rendering. We just recurse through the tree and collect what we need.
Our goal is to collect all of the middleware and their nested relationships. We use the tree data structure MiddlewareNode to store them:
export type Middleware = (
req: Request,
res: Response,
/ / recursion
recurse: () => Promise<boolean>,
) = > Promise<boolean>
export interface MiddlewareNode {
m: Middleware // Middleware functions
skip: boolean // Whether to skip
children: MiddlewareNode[] // Sub-middleware
}
Copy the code
Render function:
let currentMiddlewareNode
export function render(vnode) {
// ...
// 🔴 create the root middleware
const middlewares = (currentMiddlewareNode = createMiddlewareNode())
/ / 🔴 mount
const tree = mount(vnode)
// ...
}
Copy the code
Mounting is a recursive process in which custom components are expanded and use components are collected into currentMiddlewareNode:
function mount(vnode) {
let prevMiddlewareNode
if (typeof vnode.type === 'function') {
// 🔴 Expand custom components
const rtn = vnode.type(vnode.props)
if(rtn ! =null) {
// Recursively mount the render results of custom components
mount(rtn, inst)
}
} else if (typeof vnode.type === 'string') {
// Built-in components
if (vnode.type === 'use') {
// 🔴 collect middleware
constmd = createMiddlewareNode(inst.props.m) md.skip = !! inst.props.skip currentMiddlewareNode.children.push(md)// Save parent middleware
prevMiddlewareNode = currentMiddlewareNode
currentMiddlewareNode = md // ⬇️ push the stack to add lower-level middleware to the list
} else {
/ /... Other built-in components
}
// 🔴 mount child nodes recursively
mountChilren(inst.props.children, inst)
if (vnode.type === 'use') {
currentMiddlewareNode = prevMiddlewareNode // ⬆️ pops the stack}}}// 🔴 Mount the child node list
function mountChilren(children: any, parent: Instance) {
childrenToArray(children).forEach(mount)
}
Copy the code
5.2 run
Now let’s see how it works. We implemented a simple middleware mechanism that is a little easier to understand than Koa:
export async function runMiddlewares(req, res, current) :Promise<boolean> {
const { m, skip, children } = current
if (skip) {
// Skip and return false
return false
}
// Call the middleware
return m(req, res, async() = > {/ / recurse callback
// 🔴 Recursively calls child middleware if there are child middleware
if (children && children.length) {
for (const child of children) {
const matched = await runMiddlewares(req, res, child)
if (matched) {
// 🔴 If one of the siblings matches, none of the subsequent middleware will be executed
return true}}}return false // 🔴 No child middleware, or no child middleware matches})}Copy the code
Easy, huh? It’s recursion recursion recursion
6. Conclusion, finally done
This article has moved from configuration files to DSLS to JavaScript’s internal DSL expressions and capabilities. Finally, the focus is on JSX.
I demonstrated the componentized thinking of JSX and React with a practical case study that not only describes user interfaces, but also shows the potential and flexibility of JSX as a DSL.
Finally, summarize the advantages and disadvantages.
✅ advantages
- Better type inference and constraints. The Typescript friendly
- Can be combination. With component encapsulation and composition capabilities, can easily encapsulate advanced, easy-to-use interfaces
- Just Javascript. It’s JavaScript code itself, it’s very flexible
- Better organization, comparable to configuration files. JSX syntax is similar to XML in that it is well organized.
- Habit. If you’re used to front-end frameworks like React and Vue, the JSX configuration is easy to get used to
- Simple implementation.
- A more intuitive representation of the hierarchy. An example is the onion structure for middleware
- Modular. By nature, interfaces can be distributed to different files that can then be easily combined.
⚠ ️ shortcomings
- Code needs to be translated. Babel and Typescript translations are required.
- A little Verbose.
Flexible but organized. Flexibility often leads to disorganization, and organization may mean sacrificing flexibility, which is in some sense contradictory. Examples of balancing the two are rare, and JSX may be one. (I seem to be blowing 🐂)
🎉🎉 the code is already on Github and is currently in the prototype stage:ivan-94/jsxmockWelcome to ⭐️ and contribute.
7. Extension
- The pitfalls of DSL
- Talk about DSLS and DSL applications (CocoaPods as an example)
- JavaScript DSL sample
- How do you build a Web front-end Mock Server?
- Use SVRX for more elegant interface mocks