Simple method of rolling up a markdown editor with electron


Markdown is a necessary skill for every developer. In the process of writing markdown, we always look for a variety of editors, but each editor can only meet the needs of one aspect, but not all the needs of daily writing.

So Mengsheng tries to do it by himself, using electron to toss a markdown editor out.

Here is a list of my ideal pain points for the markdown editor:

  • Must have the drawing bed function, but also can upload directly to their own picture background, such as seven cattle;
  • The style must be customizable;
  • The exported HTML content can be directly pasted into the public number editor, directly released without formatting problems.
  • You can customize fixed modules, such as the head or tail of an article.
  • We can customize functions, such as: loading random pictures automatically, enriching the content of our articles.
  • Must be cross platform.
  • Other.

Environment building

Using electron as a cross platform development framework is the most ideal choice at present. Moreover, big guy applications such as vs Code and atom are also developed based on electron.


Using JavaScript, HTML and CSS to build cross platform desktop applications

When using electron for the first time, let’s download it and run it:

#Clone the warehouse for the sample project
$ git clone

#Enter this warehouse
$ cd electron-quick-start

#Install dependency and run
$ npm install && npm start


Vue is the leader of the current front-end framework, and it is developed by our Chinese people, so we have to accept it. I’m also a big fan of Vue. I’ve been using Vue since version 1.0.


Combine the two, which is recommended in this articlesimulatedgreg/electron-vue

vue init simulatedgreg/electron-vue FanlyMD

Install the plug-in and run:

npm installnpm run dev

Select plug-ins

1. Ace Editor

Choosing a good editor is essential:


npm install buefy vue2-ace-editor vue-material-design-icons --save

2. markdown-it

To quickly parse markdown content, I choose to use plug-ins:markdown-it

npm install markdown-it --save

3. electron-store

Since it’s an editor application, it’s necessary to store all the personalization settings and content locally, such as the style file required by the editor, the customized header and tail content, etc. Here I choose:electron-store

npm install electron-store --save


Everything is ready. Next, we start to realize the simple editing and preview function of markdown.

First looksrcFolder structure:

├── app-screenshot.jpg
├── appveyor.yml
├── build
│   └── icons
│     ├── 256x256.png
│     ├── icon.icns
│     └── icon.ico
├── dist
│   ├── electron
│   │   └── main.js
│   └── web
├── package.json
├── src
│   ├── index.ejs
│   ├── main
│   │   ├──
│   │   ├── index.js
│   │   ├── mainMenu.js
│   │   ├── preview-server.js
│   │   └── renderer.js
│   ├── renderer
│   │   ├── App.vue
│   │   ├── assets
│   │   │   ├── css
│   │   │   │   └── coding01.css
│   │   │   └── logo.png
│   │   ├── components
│   │   │   ├── EditorPage.vue
│   │   │   └── Preview.vue
│   │   └── main.js
│   └── store
│     ├── content.js
│     └── store.js
├── static
└── yarn.lock

The whole app is mainly divided into two columns. Edit the markdown content on the left side and see the effect on the right side in real time. The page view is mainly rendered by the renderer, so we firstrenderer/components/Next, create the Vue page:EditorPage.vue


Editing area

Plug in on the left:require('vue2-ace-editor'), handle real-time monitoringEditorEnter the markdown content and transfer it out.

watch: {
  input: function(newContent, oldContent) {

Among them, heremessageBusPut together the logical events related to Vue and ipcrenderermain.js

import Vue from 'vue';
import App from './App';
import 'buefy/dist/buefy.css';
import util from 'util';
import { ipcRenderer } from 'electron';

if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.config.productionTip = false

export const messageBus = new Vue({
 methods: {
  newContentToRender(newContent) {
   ipcRenderer.send('newContentToRender', newContent);
  saveCurrentFile() { }

//Listen for the newcontenttopreview event that passes url2preview to Vue
//That is, it is passed to the preview component to obtain
ipcRenderer.on('newContentToPreview', (event, url2preview) => {
 console.log(`ipcRenderer.on newContentToPreview ${util.inspect(event)} ${url2preview}`);
 messageBus.$emit('newContentToPreview', url2preview);

/* eslint-disable no-new */
new Vue({
 components: { App },
 template: '<App/>'

The content of the editor will be in real timeipcRenderer.send('newContentToRender', newContent);Issued by the main processipcMain.on('newContentToRender', function(event, content)Event acquisition.

There is only one main process for an electron application, and many localized things (such as local storage, file reading and writing, etc.) are handled by the main process.

For example, in this case, the first function to be implemented is “you can customize fixed modules, such as the head or tail of an article.”

We use a plug-in:electron-store, used to store the head and tail contents, create class:

import {
} from 'electron'
import path from 'path'
import fs from 'fs'
import EStore from 'electron-store'

class Content {
  constructor() {
    this.estore = new EStore()
    this.estore.set('headercontent', `<img src="">
        < section > < span > in this paper, the words < span > 111 < / span > need < span > < span > 1 minute < / span > < section > `)
    this.estore.set('footercontent', `<hr>
       < strong > coding01 we look forward to your continued attention < / strong >
       <img src="" alt="qrcode">`)

  // This will just return the property on the `data` object
  get(key, val) {
    return this.estore.get('windowBounds', val)

  // ...and this will set it
  set(key, val) {
    this.estore.set(key, val)

  getContent(content) {
    return this.headerContent + content + this.footerContent

  getHeaderContent() {
    return this.estore.get('headercontent', '')
  getFooterContent() {
    return this.estore.get('footercontent', '')

// expose the class
export default Content

Note: only the dead head and tail are written here.

With the head and tail content and the editor’s markdown content, we can integrate them and output them to our right sidePreviewComponent.

ipcMain.on('newContentToRender', function(event, content) {
 const rendered = renderContent(headerContent, footerContent, content, cssContent, 'layout1.html');
 const previewURL = newContent(rendered);
 mainWindow.webContents.send('newContentToPreview', previewURL);

Among them,renderContent(headerContent, footerContent, content, cssContent, 'layout1.html')The way is to put our head, tail, markdown content, CSS style, and our templatelayout1.htmlLoad. This is relatively simple. Look at the code directly:

import mdit from 'markdown-it';
import ejs from 'ejs';

const mditConfig = {
  html:     true, // Enable html tags in source
  xhtmlOut:   true, // Use '/' to close single tags (<br />)
  breaks:    false, // Convert '\n' in paragraphs into <br>
  // langPrefix:  'language-', // CSS language prefix for fenced blocks
  linkify:   true, // Autoconvert url-like texts to links
  typographer: false, // Enable smartypants and other sweet transforms
  // Highlighter function. Should return escaped html,
  // or '' if input not changed
  highlight: function (/*str, , lang*/) { return ''; }
const md = mdit(mditConfig);

const layouts = [];

export function renderContent(headerContent, footerContent, content, cssContent, layoutFile) {
  const text = md.render(content);
  const layout = layouts[layoutFile];
  const rendered = ejs.render(layout, {
    title: 'Page Title',
    content: text,
    cssContent: cssContent,
    headerContent: headerContent,
    footerContent: footerContent,
  return rendered;

layouts['layout1.html'] = `
    <meta charset='utf-8'>
    <title><%= title %></title>
      <%- cssContent %>
        <%- headerContent %>
        <%- content %>
        <%- footerContent %>

Here, use plug-insmarkdown-itTo parse the markdown content, then use EJS. Render () to populate the template’s location content. Here, it is also our goal:Style must be customizableAnd encapsulate different situations using different heads, tails, templates, and styles to provide foreshadowing

When we have content, we need to put it on the server,const previewURL = newContent(rendered);

import http from 'http';
import url from 'url';

var server;
var content;

export function createServer() {
  if (server) throw new Error("Server already started");
  server = http.createServer(requestHandler);
  server.listen(0, "");

export function newContent(text) {
  content = text;
  return genurl('content');

export function currentContent() {
  return content;

function genurl(pathname) {
  const url2preview = url.format({
    protocol: 'http',
    hostname: server.address().address,
    port: server.address().port,
    pathname: pathname
  return url2preview;

function requestHandler(req, res) {
  try {
    res.writeHead(200, {
      'Content-Type': 'text/html',
      'Content-Length': content.length
  } catch(err) {
    res.writeHead(500, {
      'Content-Type': 'text/plain'

Finally, we get the URL object and transfer it to the one on our rightPreviewComponent, i.e. throughmainWindow.webContents.send('newContentToPreview', previewURL);

Note: communication between the main and renderer processes usesipcMainandipcRendereripcMainUnable to send message toipcRenderer。 becauseipcMainonly.on()No way.send()Method. So it can only be usedwebContents

Preview area

The time used on the right side is oneiframeControl, make it into a componentPreview

  <iframe src=""/>

import { messageBus } from '../main.js';

export default {
  methods: {
    reload(previewSrcURL) {
      this.$el.src = previewSrcURL;
  created: function() {
    messageBus.$on('newContentToPreview', (url2preview) => {
      console.log(`newContentToPreview ${url2preview}`);

<style scoped>
iframe { height: 100%; }

stayPreviewComponent we use Vue’s$onMonitornewContentToPreviewEvent to load the URL object in real time.

messageBus.$on('newContentToPreview', (url2preview) => {

So far, we have basically implemented the most basic version of markdown editor,yarn run devRun to see the effect:


Using electron for the first time is superficial, but at least some knowledge has been learned:

  • Each electron application has only one main process, which is mainly used to deal with the system and create application windows. In the main process, ipcmain is used to listen for events from ipcrenderer, but there is no send method, only browserwindow. webContents.send()。
  • Each page has a corresponding renderer process for rendering the page. There are also corresponding ipcrenderer for receiving and sending events.
  • In the Vue page component, we still use the $on and ` $emit of Vue to deliver and receive messages.

Next step by step to improve the application, the goal is to meet their own needs, and then is: perhaps one day open source it.

Solve the problem of Chinese coding

Because we useiframe, so you need toiframeembedded<html></html>increase<meta charset='utf-8'>

Copy codeThe code is as follows:
The above is the whole content of this article. I hope it will help you in your study, and I hope you can support developepaer more.

The above is the whole content of this article. I hope it will help you in your study, and I hope you can support developepaer more.