DEV Community

Szikszai Gusztáv
Szikszai Gusztáv

Posted on

Mint vs JavaScript (stack)

In this post we are going to compare Mint with JavaScript, more specifically we are going to compare it with the equivalent JavaScript stack because Mint is also a framework.

This comparison will cover the following layers:

  • Language
  • Type checking
  • Development Server
  • View
  • State
  • Routing
  • Networking
  • Testing
  • Error Messages
  • Formatting
  • Production Build

Each layer has the following sections:

  • The JavaScript implementation with an example
  • The Mint implementation with an example
  • Comparison of the implementations
  • Statistics (if applicable)

Beware: it covers a lot of features so this is a lengthy article.

Language

Comparing the two languages is tricky because they are very different, so I'll just try to give a small definition for both.

There are examples through out the post which gives you a general idea on the syntax an semantics.

JavaScript

Wikipedia defines it as:

JavaScript (/ˈdʒɑːvəˌskrɪpt/),[7] often abbreviated as JS, is a high-level, interpreted programming language. It is a language which is also characterized as dynamic, weakly typed, prototype-based and multi-paradigm.

Mint

Mint doesn't have an official definition yet, so I'll try to do my best to summarize it in a short paragraph:

Mint is a programming language which compiles to JavaScript. Created specifically for writing client side web applications. It is fundamentally strongly typed, functional with some object-oriented parts sprinkled in.

Type checking

The languages should support some sort of type checking which is important because it makes our code safer and less buggy.

JavaScript

For JavaScript there are third party tools for type checking, like Flow which we will use, but before we can do actual type checking we need to compile our typed JavaScript (with type annotations) to regular JavaScript (removing type annotations) which the browser can use.

For that we will use Babel so we need to install some packages to make it work: babel-core babel-preset-react (which includes the Flow preset for some reason) also we install babel-preset-es2015 and babel-preset-stage-0 to make advanced JavaScript features available.

To configure babel we need to create a .babelrc file:

{
  "presets": ["es2015", "react", "stage-0"]
}
Enter fullscreen mode Exit fullscreen mode

Also we need to install flow-bin to do the actual type checking and flow-typed to install type definitions for the packages we use.

Mint

Mint comes with it's own type checker, so you don't need to do anything, it's automatically working under the hood.

Comparison

To get type checking in JavaScript you need a third party tool, in Mint it's built in.

Statistics

Statistics JavaScript Mint
Lines of code 4 0
Third party packages 6
babel-core
babel-preset-react
babel-preset-es2015
babel-preset-stage-0
flow-bin
flow-typed
0

Development Server

Our development environment should be able to to the following things:

  • compile our code into a single file
  • recompile the code when the source files change, and refresh the browser
  • serve static files from the directory
  • provide error messages if there is a syntax or type error
  • fall back to the index.html file if the route does not match a static file

JavaScript

To compile our code we can use Webpack with the webpack webpack-cli and webpack-dev-server packages and for using Babel we need the babel-loader package.

After installing them we configure them using the webpack.config.js file:

const path = require("path");

module.exports = {
  context: path.resolve(__dirname, "src"),
  mode: 'development',
  // The main.jsx will be compiled
  entry: {
    main: ["main.jsx"]
  },
  // This tells webpack how to resolve things
  resolve: {
    modules: [path.resolve("./src"), "node_modules"],
    extensions: [".jsx"]
  },
  module: {
    // This tells webpack to use babel
    rules: [
      {
        test: /\.jsx$/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  // Configuration for the development server
  devServer: {
    // Serve static files from the public folder
    contentBase: './public',
    // Fallback to the index.html
    historyApiFallback: {
      rewrites: [
        {
          from: '/./',
          to: '/'
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the configuration:

  • we are transforming our code using the Babel with the babel-loader package
  • setting up the fall back to index.html for the server
  • specify which files to compile and in which directories
  • specify the static file directory
  • specify the main file

After that we need to create the actual public/index.html file which will be served:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <div id="root"></div>
  <script type="text/javascript" src="/main.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

After that the development server can be started with: webpack-dev-server

Mint

In Mint the development server is built into the binary. After initializing our project with mint init the only thing we need to do is to start it with: mint start

Comparison

In Mint it's a built in feature, while in JavaScript you need to use a third party package to achieve the same setup.

Statistics

Statistics JavaScript Mint
Lines of code 44 0
Third party packages 4 webpack webpack-dev-server webpack-cli babel-loader 0

View

For the view layer the following needs to be met:

  • the styling should be scoped to the current component and it should be written in CSS, in the same file
  • the properties of component should be type checked (preferably at compile time but runtime is OK), also default value for them should be provided

For the sake of the example, we are going to implement a simple counter component:

  • it should display a counter
  • it should have two buttons, one for decrementing the counter and one for incrementing it
  • the background color should be reddish (orangered) if it's below zero, and greenish (limegreen) if it's above ten

JavaScript

Flow automatically checks prop types we just need to create a type for them and use it, for the default properties we can use a static class property, and for styling we can use styled-components.

// @flow

import React, { Component } from "react";
import styled from "styled-components";

/* This is the styling for the base div. */
const BaseDiv = styled.div`
  background: ${props => props.background};
  border-radius: 5px;
  transition: 320ms;
  display: flex;
  padding: 20px;
  margin: 20px;
`

/* This is the styling for the counter span. */
const CounterSpan = styled.span`
  font-family: sans;
  font-size: 20px;
  padding: 0 20px;
`

/* These are the property type definitons. */
type Props = {
  onIncrement: () => void,
  onDecrement: () => void,
  counter: number
};

export default class Counter extends Component<Props> {
  /* These are the default property values. */
  static defaultProps = {
    onIncrement: () => null,
    onDecrement: () => null,
    counter: 0
  }

  /* This is a function to return the background color. */
  background () {
    const { counter } = this.props

    if (counter >= 10) {
      return "lightgreen"
    } else {
      if (counter < 0) {
        return "orangered"
      } else {
        return "#F2F2F2"
      }
    }
  }

  /* Renders the component. */
  render () {
    const { counter, onDecrement, onIncrement} = this.props

    return <BaseDiv background={this.background()}>
      <button onClick={() => onDecrement()}>
        Decrement
      </button>

      <CounterSpan>
        { counter }
      </CounterSpan>

      <button onClick={() => onIncrement()}>
        Increment
      </button>
    </BaseDiv>
  }
}
Enter fullscreen mode Exit fullscreen mode

Also to be able to display our counter we need to add it to the DOM.

// @flow

/* Default react imports. */
import React, { Component } from "react";
import styled from "styled-components";
import ReactDom from "react-dom";

/* Import the counter component. */
import Counter from './counter.jsx';

/* The base style. */
const StyledDiv = styled.div`
  justify-content: center;
  flex-direction: column;
  align-items: center;
  font-family: sans;
  display: flex;
  height: 100vh;
`

/* This is our main component. */
class Main extends Component {
  render () {
    return <StyledDiv><Counter/></StyledDiv>
  }
}

/* Get the root element. */
const root = document.getElementById('root')

/* If there is a root element render the main component. */
if (root) { 
  ReactDOM.render(<Main/>, root) 
}
Enter fullscreen mode Exit fullscreen mode

Mint

In Mint you can define properties one-by-one with type and default value, styling is done with style blocks.

component Counter {
  /* Here we are defining the properties of the counter. */
  property onIncrement : Function(a) = () : Void => { void }
  property onDecrement : Function(a) = () : Void => { void }
  property counter : Number = 0

  /* This is the styling for the base div. */
  style base {
    background: {background};
    border-radius: 5px;
    transition: 320ms;
    display: flex;
    padding: 20px;
    margin: 20px;
  }

  /* This is the styling for the counter span. */
  style counter {
    font-family: sans;
    font-size: 20px;
    padding: 0 20px;
  }

  /* This is a computed property for the background color. */
  get background : String {
    if (counter >= 10) {
      "lightgreen"
    } else if (counter < 0) {
      "orangered"
    } else {
      "#F2F2F2"
    }
  }

  fun render : Html {
    <div::base>
      <button onClick={(event : Html.Event) : Void => { onDecrement() }}>
        <{ "Decrement" }>
      </button>

      <span::counter>
        <{ Number.toString(counter) }>
      </span>

      <button onClick={(event : Html.Event) : Void => { onIncrement() }}>
        <{ "Increment" }>
      </button>
    </div>
  }
}
Enter fullscreen mode Exit fullscreen mode

To display something on the screen we need to define the Main component:

component Main {
  style base {
    justify-content: center;
    flex-direction: column;
    align-items: center;
    font-family: sans;
    display: flex;
    height: 100vh;
  }

  fun render : Html {
    <div::base>
      <Counter/>
    </div>
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison

Both implementations follow pretty much the same semantics and look very similar, there are some differences though:

  • In JavaScript there are styled elements (different components), in Mint a style can be applied to an element individually
  • In JavaScript values for the styles needs to be passed explicitly, in Mint the style blocks use the same scope as functions and computed properties of the component
  • In JavaScript properties are defined in two blocks, in Mint one-by-one.
  • In JavaScript the static CSS is duplicated for each version of the style using different class names (different background color), in Mint there is only one selector using CSS variables
  • In JavaScript the text content is implicit, in Mint it is explicit

Statistics

Statistics JavaScript Mint
Lines of code 60 52
Third party packages 3
react react-dom styled-components
0

State

For the state we need a globally accessible entity, which contains the state of the application (counter) and the functions which lets us mutate it, for lack of a better term let's call it a store.

JavaScript

For JavaScript there are a lot of frameworks for handling data in an application using the store paradigm: Redux, Redux Saga, Mobx just to name a few, we are going to use Redux here.

In a new file we create the actions, action creators, reducer and finally the store.

// @flow

import { createStore } from "redux";

/* These are the actions we can take. */
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";

/* The type of the state. */
type State = {|
  counter: number
|};

/* The type of the action. */
type Action = {|
  payload: null,
  type: string
|};

/* This is the initial state. */
const initialState: State = {
  counter: 0
};

/* This is the reducer which steps the state forward. */
const reducer = function(state: State = initialState, action: Action): State {
  switch (action.type) {
    case INCREMENT:
      return { ...state, counter: state.counter + 1 };

    case DECREMENT:
      return { ...state, counter: state.counter - 1 };

    default:
      return state;
  }
};

/* This is an action creater for the increment action. */
export const increment = (): Action => {
  return {
    type: INCREMENT,
    payload: null
  };
};

/* This is an action creater for the decrement action. */
export const decrement = (): Action => {
  return {
    type: DECREMENT,
    payload: null
  };
};

/* This is a function which creates a store. */
export const store = createStore(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

After that we need to connect the store to our component:

...

/* We need to import the action creators and the store from the other file. */
import { increment, decrement } from "./store.jsx";

/* The connect comes from the react-redux package. */
import { connect } from "react-redux";

...

/* This is our main component which is connected to the store. */
class Main extends Component {
  render() {
    const { counter, onIncrement, onDecrement } = this.props;

    return (
      <div>
        <Counter
          onIncrement={onIncrement}
          onDecrement={onDecrement}
          counter={counter}
        />
      </div>
    );
  }
}

/* We need to map the state from the store to our components properties. */
const mapStateToProps = state => {
  return {
    counter: state.counter
  };
};

/* We need to map the actions from the store to our components properties. */
const mapDispatchToProps = dispatch => {
  return {
    onIncrement: () => {
      dispatch(increment());
    },
    onDecrement: () => {
      dispatch(decrement());
    }
  };
};

/*
Finally we are creating a new component by connecting the store the original one, using the two functions above.
*/
export const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Main);
Enter fullscreen mode Exit fullscreen mode

Mint

In Mint there are only two things we need to do to use a store:

Declare them:

store Store {
  /* The data lives in states. */
  state counter : Number = 0

  /* A store can have any number of functions. */
  fun increment : Promise(Never, Void) {
    /* The next statements steps the state forward based on the previous state. */
    next { counter = counter + 1 }
  }

  fun decrement : Promise(Never, Void) {
    next { counter = counter - 1 }
  }
}
Enter fullscreen mode Exit fullscreen mode

And connect them to a component:

component Main {
  /* 
  We are connecting to the store and explicitly exposing 
  it's properties and functions to be available for the 
  component.
  */
  connect Store exposing { counter, increment, decrement }

  ...

  fun render : Html {
    <div::base>
      <Counter 
        onIncrement={increment} 
        onDecrement={decrement}
        counter={counter}/>
    </div>
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison

The basic idea of the two approaches are the same, although the Redux implementation is more complex:

  • there are more types of entities (actions, action-creators, store, reducer)
  • mapping state and actions to properties
  • external functions which needed to be used correctly (connect, createStore)
  • actions have a specific type, usually with a name

In Mint everything is typed checked even the connection between a component and a store, so for example if we happen to expose something that is not available on the store we get a nice error message.

There are notable difference between the implementations:

  • Redux uses the components properties to pass the actions and data, while in Mint it's available in the components scope
  • In Redux there is a HOC component which connects the store to the base component

Statistics

Name JavaScript Mint
Lines of code 103 13
Third party packages 2 redux react-redux 0

Routing

Our example application for testing purposes should implement three routes:

  • / to display the counter with the starting value of 0
  • /10 (or any number) to display the counter with the starting value which comes from the path
  • /about to display some information about the application (dummy text is enough)

There should be links for all three routes, which the application should handle.

JavaScript

In this case we are going to use react-router.

There are a number of steps we need to take in order to make routing work.

First we need to modify our store to be able to set the count directly:

...

/* Add a new action. */
const SET = "SET";

...

/* Update the Action type. */
export type Action = {|
  payload: number | null,
  type: string
|};

...
    /* Add a new branch in the reducer for the given action. */
    case SET:
      return { ...state, counter: action.payload || 0 };
...

/* Create an action creator for the new action. */
export const set = (payload : number): Action => {
  return {
    payload: payload,
    type: SET
  };
};

Enter fullscreen mode Exit fullscreen mode

Then wen need to create a new component which handles the routing, let's call it Page

/* Import the necessary entitites. */
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

/* Import to store creator. */
import { store } from "./store.jsx";

/* 
Create a functional component which parses the count from the route
and passes it to the App component.
*/
const RoutedApp = (props) =>  {
  const parsed = Number.parseInt(props.match.params.count)
  const initialCount = parsed || 0

  /* The key is needed because the `componentDidMount` function sets the counter. */
  return <App 
    key={initialCount.toString()}
    initialCount={ initialCount} 
    store={store} />
}

/* Create a functional component which has to rooting. */
const Page = () =>
  <Router>
    <div>
      <ul>
        <li><Link to="/">0</Link></li>
        <li><Link to="/1">1</Link></li>
        <li><Link to="/about">About</Link></li>
      </ul>

      <hr/>

      <Route exact path="/about" component={<div></div>}/>
      <Route exact path="/" render={RoutedApp}/>
      <Route path="/:count" component={RoutedApp}/>
    </div>
  </Router>
Enter fullscreen mode Exit fullscreen mode

Then we need to modify our App component to set the counter when it loads.

/* Expose the set function from the store. */
import { increment, decrement, set } from "./store.jsx";

class Main extends Component {
  /* When mounted set the counter. */
  componentDidMount () {
    this.props.set(this.props.initialCount)
  }

  ...
}

const mapDispatchToProps = dispatch => {
  ... 
  /* Add a prop to dispatch the set action. */
  set: (payload : number) => {
    dispatch(set(payload));
  }
}
Enter fullscreen mode Exit fullscreen mode

Mint

First we need to add a function to our store to set the counter and a state to denote which page is the active one and a function to set the page:

store Store {
  /* Create a new state for the page. */
  state page : String = ""

  ...

  fun setCounter (counter : Number) : Promise(Never, Void) {
    next { counter = counter }
  }

  fun setPage (page : String) : Promise(Never, Void) {
    next { page = page }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we handle paths using the routes top-level block:

/* In a routes block you can define the routes of the application. */
routes {
  /* 
  This matches the /about path, needs to be first because 
  routes are matched from top to bottom. 
  */
  /about {
    /* We can directly call store functions. */
    Store.setPage("about")
  }

  /* This matches the index path. */
  / {
    /* Sequence allows us to do more things in sequence. */
    sequence {
      Store.setCounter(0)
      Store.setPage("counter")
    }
  }

  /* This matches the /10 path. */
  /:value (value : String) {
    sequence {
      /* Here we convert a string to a number safely. */
      counter = 
        value
        |> Number.fromString()
        |> Maybe.withDefault(0)

        Store.setCounter(counter)
        Store.setPage("counter")
    }
  }  
}
Enter fullscreen mode Exit fullscreen mode

Then we need to modify our Main component:

component Main {
  /* Expose the page property. */
  connect Store exposing { counter, increment, decrement, page }

  ...

  get content : Html {
    /* Decide what to render based on the page. */
    case (page) {
      "counter" =>
        <Counter
          onIncrement={increment}
          onDecrement={decrement}
          counter={counter}/>

      "about" =>
        <div>
          <{ "about" }>
        </div>

      =>
        <div>
          <{ "" }>
        </div>
    }
  }

  fun render : Html {
    <div::base>
      <ul>
        <li>
          <a href="/">
            <{ "/0" }>
          </a>
        </li>

        <li>
          <a href="/10">
            <{ "/10" }>
          </a>
        </li>

        <li>
          <a href="/about">
            <{ "/about" }>
          </a>
        </li>
      </ul>

      <{ content }>
    </div>
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison

The two implementations of the routing is fundamentally different:

  • In JavaScript it is component based (render something when the path changes), while in Mint it is action based (do something when the path changes)
  • In JavaScript the routing does not interact with the store, in Mint it does
  • In JavaScript the routing is handled by the developer, while in Mint it's handled by the runtime
  • In JavaScript a component is needed for the links, in Mint they are plain a tags and the runtime handles the navigation

Statistics

Name JavaScript Mint
Lines of code 68 47
Third party packages 1 react-router 0

Networking

To demonstrate how to fetch something from the network, a simple text files content should be displayed on the about page which is fetched dynamically.

JavaScript

In JavaScript this is straight forward to do using the Fetch API.

We are going to create a stateful component for this:

/* Import React and Component. */
import React, { Component } from "react";

/* The component for the about page. */
export default class About extends Component {
  /* In the constructor set the initial state. */
  constructor(props) {
    super(props)

    this.state = {
      /* This field is for tracking the status of the request. */
      status: "INITIAL",
      /* The content which will be displayed once loaded. */
      content: ""
    }
  }

  /* When the component is mounted. */
  componentDidMount() {
    /* Set the status as loading. */
    this.setState({ status: "LOADING" }, () => {
      /* Fetch the data. */
      fetch('/about.txt')
      .then((response) => {
        /* Get the text. */
        response
        .text()
        .then((body) => {
          /* Set the status to loaded and content. */
          this.setState({
            status: "LOADED",
            content: body
          })
        })
      })
      .catch(() => {
        /* On an error set the status. */
        this.setState({
          status: "ERRORED",
          content: ""
        })
      })
    })
  }

  render () {
    /* Based on the status render things. */
    switch (this.state.status) {
      case "LOADING":
        return <div>Loading...</div>
      case "ERRORED":
        return <div>Could not load the content...</div>
      case "LOADED":
        return <div>{this.state.content}</div>
      default:
        return false
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After we have the component we need to update or Page component to include it:

...
<Route exact path="/about" component={About}/>
...
Enter fullscreen mode Exit fullscreen mode

Mint

In Mint we need to use the Http module from the standard library mint-core which is installed automatically on project initialization.

/* Define an enum for the status. */
enum Status {
  Initial
  Loading
  Loaded
  Errored
}

/* The component for the about page. */
component About {
  /* A state to track the status. */
  state status : Status = Status::Initial

  /* A state for the content. */
  state content : String = ""

  /* When the component is mounted. */
  fun componentDidMount : Promise(Never, Void) {
    /* In a sequence expression statements are executed asynchronously in sequence. */
    sequence {
      /* Set the status to loading. */
      next { status = Status::Loading }

      /*
      Get the response and unpack it from a
      Result(Http.ErrorResponse, Http.Response).
      */
      response =
        "/about.txt"
        |> Http.get()
        |> Http.send()

      /* Set the status to loaded and the content. */
      next
        {
          status = Status::Loaded,
          content = response.body
        }
    } catch Http.ErrorResponse => error {
      /* On an error set the status to errored. */
      next
        {
          status = Status::Errored,
          content = ""
        }
    }
  }

  /* Renders the component. */
  fun render : Html {
    /* Renders things based on status. */
    case (status) {
      Status::Initial => Html.empty()

      Status::Loading =>
        <div>
          <{ "Loading..." }>
        </div>

      Status::Errored =>
        <div>
          <{ "Could not load the content..." }>
        </div>

      Status::Loaded =>
        <div>
          <{ content }>
        </div>
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Here also we need to update the Main component to display it:

...
"about" => <About/>
...
Enter fullscreen mode Exit fullscreen mode

Comparison

The implementation is basically following the same steps but there are differences:

  • In JavaScript we can use promises for asynchronous tasks, in Mint it is a language feature using the sequence expressions
  • In JavaScript we can leave out error handling, in Mint it is enforced with nice error messages
  • In JavaScript we need to use this.state and this.setStatefor state handling, in Mint it is a built in feature using the name of the states and next keywords
  • In JavaScript we need to use strings for the status in Mint we can use an enum

Statistics

Statistics JavaScript Mint
Lines of code 60 72
Third party packages 0 1 mint-core

Testing

We will write three simple test for the Counter component:

  • displays the counter properly
  • clicking on the increment button increments the counter
  • clicking on the decrement button decrements the counter

JavaScript

We will use Jest and Enzyme for testing the Counter component. Also we need to add enzyme-adapter-react-16 for Enzyme to work with React, also we need set some configuration in package.json for Jest to avoid an error:

...
 "jest": {
    "testURL": "http://localhost/"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we can create a test file for our component:

/* Import things. */
import Adapter from 'enzyme-adapter-react-16';
import React, { Component } from 'react';
import Enzyme, { mount } from 'enzyme';

/* Configure enzyme. */
Enzyme.configure({adapter: new Adapter()});

/* Import our Counter component. */
import Counter from './counter.jsx';

/* A test component which handles the state. */
class TestComponent extends Component {
  constructor(props) {
    super(props)
    this.state = { counter: 0 }
  }

  increment() {
    this.setState({ counter: this.state.counter + 1 })
  }

  decrement() {
    this.setState({ counter: this.state.counter - 1 })
  }

  render() {
    return <Counter
      onIncrement={() => this.increment()}
      onDecrement={() => this.decrement()}
      counter={this.state.counter}
      />
  }
}

it('displays the counter', () => {
  const counter = mount(<TestComponent/>);

  expect(counter.find('span').text()).toEqual('0');
});

it('decrements the counter', () => {
  const counter = mount(<TestComponent/>);

  expect(counter.find('span').text()).toEqual('0');

  // Simulate a click and update the view.
  counter.find('button').first().simulate("click")
  counter.update()

  expect(counter.find('span').text()).toEqual('-1');
});

it('increments the counter', () => {
  const counter = mount(<TestComponent/>);

  expect(counter.find('span').text()).toEqual('0');

  counter.find('button').last().simulate("click")
  counter.update()

  expect(counter.find('span').text()).toEqual('1');
});
Enter fullscreen mode Exit fullscreen mode

To run the tests we just run: jest

Mint

In Mint the language has two keywords specifically for testing: suite and test, with them we can create tests easily:

/* Create component for testing the counter which contains the state. */
component TestCounter {
  state counter : Number = 0

  fun render : Html {
    <Counter
      onIncrement={() : Promise(Never, Void) => { next { counter = counter + 1 } }}
      onDecrement={() : Promise(Never, Void) => { next { counter = counter - 1 } }}
      counter={counter}/>
  }
}

/* A suite is a group of tests. */
suite "Counter" {
  test "Displays counter" {
    /*
    We are using the Test.Html module for testing. The with keyword
    allows us to call its functions in the current scope.
    */
    with Test.Html {
      <TestCounter/>
      |> start()
      |> assertTextOf("span", "0")
    }
  }

  test "Clicking on increment increments the counter" {
    with Test.Html {
      <TestCounter/>
      |> start()
      |> assertTextOf("span", "0")
      |> triggerClick("button:last-child")
      |> assertTextOf("span", "1")
    }
  }

  test "Clicking on decrement decrements the counter" {
    with Test.Html {
      <TestCounter/>
      |> start()
      |> assertTextOf("span", "0")
      |> triggerClick("button")
      |> assertTextOf("span", "-1")
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

To run the tests just call the binary with the test command: mint test

Comparison

Both implementations are integration tests:

  • In JavaScript to run tests we need third party packages, in Mint it's built in
  • In JavaScript Jest runs the tests using nodejs while Mint runs the tests in an actual browsers
  • In Mint there is a test server which allows for manually testing in the browser

Statistics

Name JavaScript Mint
Lines of code 62 47
Third party packages 3 jest enzyme enzyme-adapter-react-16 0

Error Messages

Our development environment should provide nice easy to understand error messages.

JavaScript

In JavaScript we need to handle three types or errors, of which only one of can be displayed in the browser:

  • Compile time errors from Webpack
  • Type errors from Flow
  • Runtime errors from the browser

To enable compile time errors we need to add the following line to our webpack.config.js:

...
devServer: {
  overlay: true
...
Enter fullscreen mode Exit fullscreen mode

Webpack Error

Flow errors can only be displayed in the console after running the binary:

Flow Error

Runtime errors can be seen in the browsers console.

Mint

In Mint there are a number of error types (syntax, type, etc...) but all of them are displayed in the same way either in the console (when running console only commands) or in the browser but with exactly the same content:

Mint Browser Error

Mint Console Error

Runtime errors can be seen in the browsers console, although they should not happen because of the type system.

Comparison

Mint errors tend to be more informative for example when miscalling a function the message shows the where the function is called and it's source.

Formatting

It is a standard practice to format our source code to a specific style, our environment should support that.

JavaScript

To format our JavaScript files we only need to install Prettier which can handle a number of languages not just JavaScript.

After installing we only need to call prettier src/* --write and our code is formatted in place.

Mint

Mint has a built in formatter which can be invoked with the mint format command, also the development server can be configured to format files when they change with the --auto-format argument.

Comparison

It's equally simple to format our code in both languages the only difference is that in JavaScript it's a third party tool.

Statistics

Name JavaScript Mint
Lines of code 0 0
Third party packages 1 prettier 0

Building production files

Our application is ready to be deployed to production, but for that we need to produce compressed and minified files. Also it would be nice to generate favicons from a base icon.

JavaScript

To minify our JavaScript output we will use UglifyJs via the uglifyjs-webpack-plugin plugin. To generate favicons we need to install the html-webpack-plugin and favicons-webpack-plugin plugins.

After installing them it we need to configure them in our webpack.config.js file:

/* Import them. */
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
  plugins: [
    new FaviconsWebpackPlugin('../public/logo.png'),
    new HtmlWebpackPlugin({
      template: '../public/index.html'
    })
  ],
  optimization: {
    minimizer: [
      new UglifyJsPlugin()
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Also we need to create a new configuration for the build webpack.prod.config.js:

const common = require('./webpack.config.js');

module.exports = {
  ...common,
  mode: 'production'
}
Enter fullscreen mode Exit fullscreen mode

After that can just call webpack --config webpack.prod.config.js to build our production files into the dist directory.

Mint

Currently Mint does not minifies the production files, although it can be minified with any tool out there. It is planned to be implemented soon.

To generate favicons, we only need to specify the icon for the application (this feature requires ImageMagick to be installed):

...
  "application": {
    ...
    "icon": "assets/logo.png"
  }
...
Enter fullscreen mode Exit fullscreen mode

Then we can generate the production files to the dist directory with the mint build command.

Bonus: Progressive Web Application

Mint by default generates the manifest.json and a service worker for all applications, all we need to do is set the corresponding fields in the mint.json file:

{
  ...
  "application": {
    ...
    "icon": "assets/logo.png",
    "name": "Counter",
    "orientation": "portrait",
    "display": "standalone",
    "theme-color": "#FFF",
    "meta": {
      "viewport": "width=device-width, initial-scale=1, shrink-to-fit=no",
      "description": "A simple counter example",
      "charset": "utf-8"
    }
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

After building it again and deploying it to a secure website (with https) it is installable on mobile phones.

Comparison

Using JavaScript third party tools are needed for building the production files, in Mint it's a built in feature.

Statistics

Name JavaScript Mint
Lines of code 14 1
Third party packages 3
html-webpack-plugin
uglifyjs-webpack-plugin
favicons-webpack-plugin
0

Overall Statistics

Here you can find the final statistics of the two implementations (all of them are collected on a single machine during the same conditions):

Name JavaScript Mint
Lines of code (wc -l) 408 258
Production build time 21.36 s 854 ms
Command line utilities used 6 1
Third party packages 24 1
All installed packages 1426 1
Packages size (node_modules / .mint) 296 MB 744 kB
Bundle Size (dist directory) 1.3 MB 315 kB
Bundle Size (.js) 212 kB (minified) 204 kB (unminifed)
176 kB (minified)

As you can see above the main difference is in the third party packages and packages size. In JavaScript it's bigger because it contains the tools as well.

Ending Thoughts

This part is probably subjective (since I'm the author of Mint) so take it as is.

In my opinion this really shows how over engineered todays front-end development is (1426 packages for such a simple application??). Also it's not enough that a developer needs to learn basics of web development (HTML, CSS, JavaScript) they need to learn all of these dependencies as well, which come with their own documentation and that can be overwhelming.

This is basically why I created Mint so it would be easier to write web applications without all the hassle. I hope this article shows what Mint can do and how easy it is to use it.

If I piqued your interest you can find all the information to get started on the website or if you like to contribute check out the Github repository:

GitHub logo mint-lang / mint

🍃 A refreshing programming language for the front-end web

Mint

CI Discord Backers on Open Collective Sponsors on Open Collective

A refreshing programming language for the front-end web, aiming to solve the most common issues of Single Page Applications (SPAs) at a language level:

  • Reusable components
  • Styling
  • Routing
  • Global and local state handling
  • Synchronous and asynchronous computations that might fail

While focusing on:

  • Developer happiness
  • Fast compilation
  • Readability

Project Status

The project is in development, we are still tweaking the language and standard library.

There are some bigger applications which can be used as examples / learning material:

Installing

Follow these instructions

Documentation

Community

Questions or suggestions? Ask on Discord

Also, visit Awesome Mint, to see more guides, tutorials and examples.

Contributing

Read the general Contributing guide, and then:

  1. Fork it (https://github.com/mint-lang/mint/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit

The full code for both applications can be found here (on separate branches):

Mint Vs. X

This repository contains the implementation of a sample application which is used in "Mint vs X" blog posts of comparing Mint with popular frontend frameworks / languages

Implementations

All of the implementation lives in separate branches:

  • Mint - The base implementation in Mint
  • JavaScript - A JavaScript implementation using React + Redux

Blog Posts




Also if you are interested in more applications written in Mint I suggest you check out these repositories:

GitHub logo mint-lang / mint-website

The website of the Mint programming language

GitHub logo mint-lang / mint-realworld

Mint implementation of https://realworld.io frontend

Mint Realworld

Mint codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API.

Demo    RealWorld

This codebase was created to demonstrate a fully fledged fullstack application built with Mint including CRUD operations, authentication, routing, pagination, and more.

We've gone to great lengths to adhere to the Mint community styleguides & best practices.

For more information on how to this works with other frontends/backends, head over to the RealWorld repo.

How it works

This implemenation only uses the Mint language and it's standard library without any third party dependencies.

To learn more about Mint check out the Guide

Differences

There are a few difference to other implementations:

  • since Mint has a built in way of styling HTML elements we wanted to showcase that, so the design of the application greatly differs from the original one
  • the end result is also a Progressive Web…

Top comments (0)