preface

The term DSL has come up a lot in the front-end community in recent years, and this has a lot to do with the changing environment:

  1. React, Vue, Angular, and other modern frameworks often have a strong DSL connection to their presentation layer design, and we can get some practical guidance from these excellent works.
  2. The transition tool chain of front-end related languages is becoming more mature. Tools such as Babel, PostCSS and so on can help developers to participate in the language building process with low cost in the form of plug-ins.
  3. Community parser generation tools, such as Jison, PEg.js, and others, have become ubiquitous, helping developers quickly implement entirely new programming languages (typically external DSLS such as templates).

While the practice of “technology” began to flourish, it also led to some misconceptions and myths, such as the equalization of DSL and transcompile as purely technical issues, the confusion of the boundary between the internal DSL and the library (interface), and so on. DSL became an unfamiliar word that everyone was talking about.

At the same time, authoritative books such as Martin Fowler’s Domain-Specific Language tend to favor the Tao solution, but are filled with obscure cases such as “Miss Grant’s Chamber of Secrets Controller” and “Bountiful Securities Corporation” that are not well suited to domestic front-end developers. In fact, the daily work of the front end is already so intertwined with THE DSL that as a developer you don’t need to go through these rough cases to learn the DSL.

Due to the particularness of working experience, the author of this paper has accumulated some practical experience on front-end DSL (mainly external DSL), which is also reflected in the open source project he maintains. At the same time, the author also has some unsystematic answers in the community, such as “How to write a Compilation tool similar to LESS”. This time I will attempt to fully explore the “hard to detail” topic of DSLS from a front-end development perspective.

Due to the length, this paper will be divided into two parts:

  • Part I: DSL introduction + internal DSL;
  • Part II: Summary of external DSL + front-end DSL practices.

DSL met

Like many concepts in computing, DSLS come first and then they are defined.

DSL is Domain Specific Language. In the book Domain Specific Language, it has a definition:

A programming language with limited expressiveness designed for a specific domain

The evolution of programming languages is a process of continuous abstraction, from machine language to assembly language and then to high-level languages like C or Ruby:

As shown above, assembly language greatly enhances the readability and maintainability of machine language by replacing machine instruction opcodes with mnemonics. But at heart it is still a low-level programming language for hardware systems such as processors and registers. The emergence of high-level language to solve this problem, really separated from the direct association of machine instruction set, the above layer of abstract statements (flow control, loop, etc.) and data structure more close to natural language and mathematical formula to complete the coding work, greatly improve the efficiency of program development.

But at the high-level level, the efficiency gains of abstraction seem to have a ceiling. Whether it is from C to Java, or the more abstract programming languages derived from various programming paradigms, they all solve general programming problems. They all have sufficient process abstraction and data abstraction, resulting in a large number of concepts, and thus affect the programming efficiency.

However, in some specialized fields, task processing does not need so many language features. DSL is a breaking solution generated in this contradiction. It is a language tool for solving specific tasks, such as document compilation with Markdown, string matching with RegExp, task control with make, gradle, etc. Data lookup has SQL, Web style coding has CSS, and so on. Its essence is the same as many of our software engineering problems, by limiting the boundaries of the problem domain, so as to lock down the complexity and improve the efficiency of programming.

Let’s start with a simple example, such as two weeks ago:

Method a

new Date(Date.now() - 1000 * 60 * 60 * 24 * 7 * 2);
Copy the code

Method 2

2 weeks ago
Copy the code

Method three

(2).weeks().ago();
Copy the code

Solution one is a solution that fits into general programming thinking, but even as programmers we can’t see what it means at a glance.

Solution 2 and solution 3 are two different types of DSLS — external and internal DSLS. They are more intuitive (ask your girlfriend), but they don’t run directly, and if you try to run them in JavaScript, you’ll get completely different errors:

  • 2 weeks agoWill getUncaught SyntaxError: Unexpected identifierGrammar mistakes.
  • (2).weeks().ago()I’m going to get oneUncaught TypeError: 2.weeks is not a functionRuntime type error.

In fact, the types of errors we see are fundamentally different.

External DSL Briefs

The second solution is called an external DSL, which is an independent programming language that needs to implement its own compilation tools starting from the parser, which is expensive to implement. However, its syntax is more flexible, and it is easier to meet the expressive needs of users.

The direct counterpart of an external DSL is GPPL, which is less difficult to implement than GPPL because it has fewer restricted syntax features and generally does not require Turing completeness.

GPPL stands for “General Purpose Programming Language”, also known as general-purpose Programming languages, such as JavaScript, which are designed to solve general-purpose Programming problems.

Common front-end template engines like Mustache and the JSX syntax supported by React and Vue are external DSLS.

Examples of Mustache:

<h2>Names</h2>
{{#names}}
  <strong>{{name}}</strong>
{{/names}}
Copy the code

This is much more efficient than assembling strings manually.

Internal DSL brief

Solution 3: Embedded DSL or Internal DSL (Embedded DSL or Internal DSL) is a special DSL built on top of other host languages (generally GPPL). It shares infrastructure such as compilation and debugging tools with host languages, and is cheaper to learn and easier to integrate. It is syntactically homologous to the host language, but requires additional encapsulation at runtime.

You can also think of an internal DSL as a special interface encapsulation style for a specific task, as jQuery can be thought of as an internal DSL for DOM manipulation.

The syntactic flexibility and syntactic noise of internal DSLS often depends on the choice of host language, and in this example we’ll focus on JavaScript.

syntactic noise is syntax within a programming language that makes the programming language more difficult to read and understand for humans.

In short: Watch an egg hurt, write an egg hurt.

Finally, let’s look at the relationship between internal DSLS and external DSLS and GPPL:

The definition of an internal DSL has been the focus of much debate in the community. To understand what an internal DSL is, let’s familiarize ourselves with a typical build style for an internal DSL.

Internal DSL Style Guide (JavaScript Description)

There are several styles that can be applied to building an internal DSL with JavaScript.

Style 1: Cascading approach

Cascading methods are the most common pattern for internal DSLS, so let’s start with native DOM manipulation as an example:

const userPanel = document.querySelector('#user_panel');

userPanel.addEventListener('click', hidePanel);

slideDown(userPanel); // Suppose this is an implemented animation encapsulation

const followButtons = userPanel.querySelectorAll('button');

followButtons.forEach(node= > {
  node.innerHTML = 'follow';
});
Copy the code

It’s hard to see what you’re doing at a glance, but let’s say we use the ancient framework jQuery to achieve the equivalent:

$('#user_panel')
  .click(hidePanel)
  .slideDown()
  .find('button')
  .html('follow');
Copy the code

It’s easy to understand what it means:

  1. find#user_panelNode;
  2. Set to hide it after clicking;
  3. Downward dynamic effect expansion;
  4. Then find all the button nodes below it;
  5. Fill in the follow content for these buttons.

The core of chained invocation styles, such as cascading methods, is that the invocation no longer designs a specific return value, but returns directly to the next context (usually itself), thus enabling cascading invocation.

Style 2: Cascading pipes

Cascaded pipes are just a special application of the cascaded method, exemplified by GULP:

Gulp is a make-like build task management tool that abstracts files into a type called Vinyl(Virtual File Format) and uses the Pipe method to complete tasks in Transformer.

gulp.src('./scss/**/*.scss')
  .pipe(plumber())
  .pipe(sass())
  .pipe(rename({ suffix: '.min' }))
  .pipe(postcss())
  .pipe(dest('./css'))
Copy the code

Gulp will be familiar to many, as its design philosophy is derived from pipes on the Unix command line. This example can be directly analogous to the following commands:

cat './scss/**/*.scss' | plumber | sass | rename --suffix '.min' | postcss | dest './css/'
Copy the code

The above abstraction for pipelines can also be used to build DSLS in the same way as regular cascading calls, such as chajs:

cha()
  .glob('./scss/**/*.scss')
  .plumber()
  .sass()
  .rename({ suffix: '.min' })
  .postcss()
  .dest('./css')
Copy the code

The above is just a syntax analogy for DSL, chajs doesn’t necessarily have overland or other functional modules.

With the reduction of multiple pipes, the code is obviously reduced, but there is no greater improvement in fluency.

Second, the chaJS style requires that these extension methods be registered in the instance, which adds to the integration cost, and the integration code also affects the fluency of the DSL.

cha
  .in('glob'.require('task-glob'))
  .in('combine'.require('task-combine'))
  .in('replace'.require('task-replace'))
  .in('writer'.require('task-writer'))
  .in('uglifyjs'.require('task-uglifyjs'))
  .in('copy'.require('task-copy'))
  .in('request'.require('task-request'))
Copy the code

Gulp, by contrast, abstracts the extension into an external Transformer, which is clearly more elegant.

Style 3: Cascading properties

(2).weeks().ago() is a much better way to do this. We can do it by static proxying properties. The key is Object.defineProperty(), It can hijack the setter and getter of a property:

const hours = 1000 * 60 * 60;
const days = hours * 24;
const weeks = days * 7;
const UNIT_TO_NUM = { hours, days, weeks };

class Duration {
  constructor(num, unit) {
    this.number = num;
    this.unit = unit;
  }
  toNumber() {
    return UNIT_TO_NUM[this.unit] * this.number;
  }
  get ago() {
    return new Date(Date.now() - this.toNumber());
  }
  get later() {
    return new Date(Date.now() + this.toNumber()); }}Object.keys(UNIT_TO_NUM).forEach(unit= > {
  Object.defineProperty(Number.prototype, unit, {
    get() {
      return new Duration(this, unit); }}); });Copy the code

Paste the above code into the console and type (2).weeks.ago to try it out. You can see that cascading properties can be expressed more succinctly than cascading methods, but also lose flexibility at the parameter level.

Some might wonder why 2.weeks.ago is not a JavaScript “Feature”. The only solution is to use a less syntactically noisy host language such as CoffeeScript.

In DSL style, no matter cascading methods, cascading pipes or cascading properties, the essence is chain invocation style. The core of chain invocation is context passing, so whether the return entity of each invocation conforms to the mind of the user is an important basis for the success of DSL design.

Style 4: Nested functions

There are also some hierarchical abstraction scenarios in development, such as DOM tree generation. Here is an example of a purely imperative build using the DOM API:

const container = document.createElement('div');
container.id = 'container';
const h1 = document.createElement('h1');
h1.innerHTML = 'This is hyperscript';
const list = document.createElement('ul');
list.setAttribute('title', title);
const item1 = document.createElement('li');
const link = document.createElement('a');
link.innerHTML = 'One list item';
link.href = href;
item1.appendChild(link1);
const item2 = document.createElement('li');
item2.innerHTML = 'Another list item';
list.appendChild(item1);
list.appendChild(item2);

container.appendChild(h1);
container.appendChild(list);
Copy the code

This is a bit opaque, and it’s hard to see the final HTML structure at a glance, so how do you build an internal DSL to smooth out this level of abstraction?

Some people have tried to implement it in a similar way to chain calls, such as concat.js:


builder(document.body)
  .div('#container')
    .h1().text('This is hyperscript').end()
    .ul({title})
      .li()
        .a({href:'abc.com'}).text('One list item').end()
      .end()
      .li().text('Another list item').end()
    .end()
  .end()
Copy the code

This seems a lot better than the imperative, but there are problems with building this DSL:

  1. Because the key to chain calls is context passing, extra is needed in hierarchical abstractionsend()Out of the stack action to achieve context switch.
  2. Readability depends on manual indentation, and the editor’s automatic indentation often breaks this harmony.

As a result, general hierarchical abstractions are less likely to use the chain-call style to build DSLS and more likely to use basic nested functions.

Take DOMBuilder, another cremains open source project, as an example:

Forget about the use of with itself

with(DOMBuilder.dom) {
  const node =
    div('#container',
      h1('This is hyperscript'),
      ul({title},
        li(
            a({herf:'abc.com'}, 'One list item')
        ),
        li('Another list item'))}Copy the code

You can see how hierarchical abstraction can be implemented more smoothly using nested functions.

If described in CoffeeScript, the syntax noise can be reduced even further, approximating the syntax of an external DSL like PUG:

div '#container',
  h1 'This is hyperscript'
  ul {title},
    li(
      a href:'abc.com'.'One list item'
    )
    li 'Another list item'
Copy the code

CoffeeScript is a language that compiles to JavaScript. It is designed to remove the bad stuff of JavaScript language design and add a lot of syntactic sugar. It has influenced the evolution of many subsequent JavaScript standards.

Nested functions are essentially embedding context switches that need to be handled in chain calls into function nesting operations, so they are well suited in hierarchical abstraction scenarios.

In addition, the application of nested functions in DSLS is similar to that of parsing trees, because it follows the idea of syntax tree generation and can be directly mapped into corresponding external DSLS, such as JSX:

<div id='container'>
  <h1 id='heading'> This is hyperscript </h1>
  <ul title={title} >
    <li><a href={href} > One list item </a></li>
    <li> Another list item </li>
  </ul>
</div>
Copy the code

Nested functions are not a panacea and are not naturally suited for sequence-sensitive scenarios such as process, time, etc.

If the cascading pipe of style 2 is modified to nested functions:

The execution logic is obviously inconsistent with the reading order, and will increase the writing burden (at the same time, concern about open and closed logic), which greatly affects the reading and writing fluency.

Style 5: Object literals

Many DSLS in the industry are similar to configuration files, such as JSON, YAML, and other external DSLS that are expressive in nested data presentations.

JavaScript also has a feature that lends itself to building DSLS in this scenario: literal objects. In fact, JSON (full name JavaScript Object Notation) is a derivative of this feature, becoming a standard data interchange format.

For example, in project puer, the routing configuration file selects the JS object literal instead of JSON:

module.exports = {
    'GET /homepage': './view/static.html'
    'GET /blog': {
        title: 'Hello'
    }
    'GET /user/:id': (req, res) = >{
        res.render('user.vm')}}Copy the code

JSON’s natural drawback is that it is serializable, which greatly limits its expressiveness (but also makes it the most popular format for cross-language data exchange). For example, the last item in the previous example also introduces functions, which are “less pure” from a DSL perspective, but more functional. This is why some build task-related DSLS (make, Rake, cake, Gradle, etc.) are almost entirely internal DSLS.

In addition, object literals can improve the readability of arguments due to the presence of object keys, such as:

div({id: 'container'.title: 'This is a tip' })

// CoffeeScript Version
div id: 'container'.title: 'This is a tip'
Copy the code

Obviously more readable than the following example with fewer words:

div('container'.'This is a tip')
Copy the code

Building DSLS is not always as simple as possible; fluency is the key.

Object literals have a strong structure and are generally used for data abstraction scenarios such as configuration, but not for process abstraction scenarios.

Style 6: Dynamic proxy

One of the classic flaws in the construction of the internal DSLS listed earlier is that they are statically defined properties or methods, not dynamic.

As mentioned in the previous section [Style 4: Nested Functions], concat.js has all methods like div, p, and so on defined statically. In fact, this static exhaustion is a pitfall because of the Custom Elements feature, not to mention the HTML standard itself is constantly adding new tags.

This problem does not exist in external DSLS, such as regularJS/Regular, which I wrote earlier. Its built-in template engine matches text like /<(\w+)/ into a uniform TAG lexical element at the lexical parsing stage, thus avoiding overuse.

To implement this feature, internal DSLS rely heavily on the metaprogramming capabilities of the host language. One feature Ruby often uses as a typical host language to demonstrate its metaprogramming power is method_missing, which accepts all undefined methods dynamically. The most direct feature is dynamically named methods (or meta-methods), which solves the problem of internal DSLS being named statically as mentioned above.

Fortunately, there is also a more powerful language feature in JavaScript, Proxy, which can be used to delegate attribute fetching, thus solving the above concat.js exhaustion problem.

The following is not the complete code, but a brief demonstration

function tag(tagName){
    return {tag: tagName}
}

const builder = new Proxy(tag, {
  get (target, property) {
    return tag.bind(null, property)
  }
})

builder.h1() // {tag: 'h1'}
builder.tag_not_defined()  // {tag: 'tag_not_defined'}
Copy the code

Proxy makes JavaScript extremely meta programming, and in addition to easily emulating Ruby’s smug method_missing feature, it has many other dynamic Proxy capabilities that are important tools for implementing internal DSLS.

Style 7: Lambda expressions

There are a number of query libraries on the market that use the chained style, which is very close to the way SQL itself is written, such as:

const users = User.select('name') 
  .where('id==1');
  .where('age > 1');
  .sortBy('create_time')
Copy the code

To convert an expression such as id==1 into a runnable filter, we have to implement a full expression parser to compile the equivalent function

function(user){
    return user.id === 1
}
Copy the code

The implementation cost is very high, and lambda expressions can solve this requirement more cheaply

const users = User.select('name')
  .where(user= > user.id === 1);
  .where(user= > user.age > 20);
  .sortBy('create_time')
Copy the code

Examples of this already exist, such as language-integrated Query (LINQ) based on C#, which is the most active example in internal DSL technology circles.


var result = products
    .Where(p => p.UnitPrice >= 20)
    .GroupBy(p => p.CategoryName)
    .OrderByDescending(g => g.Count())
    .Select(g => new { Name = g.Key, Count = g.Count() });

Copy the code

Lambda expressions are essentially intuitive and lazy logical expressions that avoid extra parsing work, but they rely heavily on host language features (anonymous functions + arrow representations) and can introduce a certain amount of syntax noise.

Style 8: Natural language abstraction

Natural language abstraction is to design the syntax of a DSL in a way that more closely resembles natural language. The basic logic of this works is that domain experts are basically natural people like you and me, who are more receptive to natural language syntax.

The nature of natural language abstraction is syntactic sugar, and unlike GPPL syntactic sugar, DSL syntactic sugar is not necessarily the most concise, but instead adds “redundant” non-functional syntactic vocabulary.

For example, in the Cloud Music team’s open source SVRX (Server-X) project (a plug-in dev-Server platform), routing is a heavily used feature, and we designed an internal DSL for developers to use, as shown in the following example:

get('/blog/:id').to.send('Demo Blog')

put('/api/blog/:id').to.json({code: 200})

get('/' (. *)).to.proxy('https://music.163.com')
Copy the code

To is a non-functional word, but it makes the whole statement more understandable to human beings (and, of course, to us programmers).

The advantage of an internal DSL is exploited in a unit test scenario through natural language abstraction. For example, if we use a bare assert method like Assert, a unit test case might look like this:

var foo = '43';

assert(typeof foo === 'number'.'expect foo to be a number');
assert(
  tea.flavors && tea.flavors.length === 3.'c should have property flavors with length of 3'
)
Copy the code

There are several notable issues that need to be optimized:

  1. Imperative assertion statements are not intuitive to read;
  2. For readability of the report, you need to pass in additional prompts (e.gexpect foo to be a number).

If the case were written based on CHAI, the readability would be immediately improved:

var foo = '43'

// AssertionError: '43' should be a 'number'.
foo.should.be.a('number');

tea.should.have.property('flavors').with.lengthOf(3);

Copy the code

You can see that test cases are much easier to read and write, and that when assertions fail, more user-friendly error messages are automatically assembled based on the state generated by chain calls, especially when combined with testing frameworks such as Mocha to generate intuitive test reports:

Program statements can be made more intuitive by adding natural language-like auxiliary syntax (verbs, names, prefixes, subfixes, etc.).

Style summary

This article does not cover all of the internal DSL implementation styles (for example, there are also some decorator-based gameplay), and none of the styles listed are silver bullets, they all have their own scenarios, and they complement each other.

Some myths about internal DSL

With the introduction of some idiomatic styles above, we have established some understanding of the internal DSL of the front end. In this section, we will discuss the “Why” question in depth:

Why JavaScript as the host language

As you can see from the style case, the host language directly determines the upper limit of “syntactic” optimization for the internal DSL. As ROR is for Ruby and Gradle is for Groovy, the early choices typically outweigh the effort. JavaScript, the most convenient language for front-end development, actually has an advantage when building internal DSLS because of its hodgepodge of language features:

  • Draw lessons from Java language data type and memory management, high degree of abstraction.
  • Based on the object, and has a convenient object literal representation, data expression power first-class.
  • The function is first class and can have some generic FP applications.
  • Use prototype-based inheritance and extend primitive types such as Number.
  • With the support of Proxy, Reflect and other new features, I have a strong metaprogramming ability.

Its Bohemian language features allow it to handle almost any internal DSL construction style, and its ridiculously active community provides a natural developer base.

JavaScript’s natural weakness is its c-derived syntax, which makes it noisy, and using a variant such as CoffeeScript can reverse some of this weakness.

Libraries (interfaces) or internal DSLS

The boundary issue for external DSLS is often the difference between DSL and GPPL, which is not much of a debate in the community. The discussion of internal DSLS, and in particular the differences with libraries (interfaces), has been a bit of a blur.

In fact, DSLS are also known as fluent interfaces, so they are themselves a mode of interface encapsulation or library encapsulation that aims at extreme expressiveness. However, it has several significant design differences compared to traditional interface encapsulation:

  • Linguistic.
  • Not constrained by traditional programming best practices: command-query separation, Demeter’s law, etc.

For example, in an internal DSL, the resulting code such as foo.should.be.a.number is like a whole sentence associated in a given syntax, rather than a collection of imperative code. In jQuery, HTML is both a query method (.html()) and a command method (.html(‘content to set’)). The primary goal of their design is “extreme fluency of expression” rather than the traditional encapsulation abstraction criteria of clear responsibility and reduced coupling.

In fact, this article is more in agreement with the point cited by Yukihiro Matsumoto in The Future of Code, which finally solves the author’s confusion and perplexity about internal DSLS:

Library design is language design

Programming language determines the basic grammar framework and only a small amount of vocabulary, library should design the pool and serves as a vocabulary of combination of classes, methods, properties and variables, and the organic combination of them according to the semantic and eventually achieve “under the limited task programming workers only need to pay attention to What, without having to pay close attention to How” the design goal. This is where the magic of 2. Weeks. Ago comes in.

So instead of trying to define a clear boundary for the internal DSL, improve your interface design according to its requirements. Here’s another, more radical idea:

Programming is a process of designing DSL for your own application.

Some potholes in internal DSL practices

In addition to the lack of functionality and additional syntactic noise due to reliance on the host language, there are other issues with internal DSLS that cannot be ignored.

Unfriendly exception

In the [style 3: Cascading properties] case, we did not define minutes. If you use (5).minutes.later, you will get the following error message:

Uncaught TypeError: Cannot read property 'later' of undefined
Copy the code

Instead of the error message we expected:

Uncaught SyntaxError: Unexpected unit minutes
Copy the code

This is because the exception handling mechanism also follows the host language. DSL abstraction at the library encapsulation level is still not able to escape this limitation, which is also the advantage of external DSLS. However, based on the Proxy mentioned in Style 6: Dynamic Proxy, we can still make some minor optimizations:

const UNITS = ['days'.'weeks'.'hours'];

const five = new Proxy(new Number(5), {
  get (target, property) {
    if(UNITS.indexOf(property) === - 1) {throw TypeError(`Invalid unit [${property}] after ${target}`)}else{
       // blablabla}}})Copy the code

Paste it into the console and type five. Minutes. You’ll get a more friendly error message:

Uncaught TypeError: invalid units [minutes] after 5
Copy the code

It’s easy to overlook the design beneath the iceberg

The design of an internal DSL is focused on the fluency of the presentation layer, and the lack of an abstract encapsulation requirement for the underlying domain model may result in the “core” of the DSL being poorly designed. When practicing DSLS, we still follow best programming practices at the domain model level, such as the domain model entities such as Duration behind 2.weeks.later in this article.

The author has extended a jja-like smooth API interface for a large and long-standing front-end framework inside (2012 do not spray) and removed the comments in less than 500 lines of code, largely due to the deep design and consistency of the framework’s underlying layer, not my DSL syntax sugar wrapper.

In addition, syntax is as important as semantics in DSL design, and the examples above demonstrate that simplicity of syntax does not necessarily lead to fluency, but must be designed in conjunction with semantic models.

On grammar and semantics: | a | b and a or b grammar is different, but the same semantic; A > B (Java) and a > B (Shell) have the same syntax but different semantics

These recommendations are equally important in the design of external DSLS.

Editor support

Some internal DSLS rely on typography for best performance. The automatic formatting engine for most languages (including external DSLS) is based on syntax tree parsing, but internal DSLS are not so lucky because they are not defined at the actual syntax level. As a result, it is often the case that an editor uses “Format Document” and loses all its effort, which is less common in indentment-based languages.

Special code highlighting is more difficult, and even auto-completion requires some extra work to be supported.

summary

Conventional programming solutions express more “How”, that is, the details of How to achieve, involving expressions, statements, data structures and other programming elements will affect the field workers’ understanding of the original problem. The secret of DSL lies in its emphasis on “What”, transforming original imperative programming into extreme declarative expressions, making DSL powerful self-explanatory, thus improving programming efficiency and even giving power to users without programming experience.

This paper focuses on the front end practice of internal DSL, an important branch, and illustrates eight implementation styles combined with Javascript and some typical examples of the front end field. It is emphasized that these styles are not independent “silver bullets”, but complement each other.

This article also addresses some of the myths, exploring the feasibility of Javascript as an internal DSL host language, emphasizing that the design guidelines of the DSL should be more of a concern than its boundary definitions, and finally bringing up some common pitfalls in the internal DSL design process.

Further reading

Stay tuned for the second part of this article, external DSLS, and the following books can help you further your learning:

  • Domain-specific Languages by Martin Fowler
  • Domain-specific Language In Action by Debasish Ghosh

The relevant data

  • The Future of Code by Yuhiro Matsumoto
  • Javascript were born
  • Declarative programming is it a real thing?
  • Talk about DSLS and DSL applications (CocoaPods as an example)
  • Construct an internal DSL with a smooth interface

This article is published from netease Cloud music front end team, the article is prohibited to be reproduced in any form without authorization. We’re always looking for people, so if you’re ready to change jobs and you like cloud music, join us!