OnlineCodeEditor is an open source project similar to Codepen developed by the author based on Vue3 + Typescript. This article records the technologies and implementation principles used in some projects.

  • 🏹 making
  • 🌈 Simple Demo

🚀 Feature

✅ Pure front-end project static deployment (use iframe and postMessage to generate live preview subpages)

✅ responsive layout, flexible layout supports drag and drop to change width and fold.

✅ HTML/CSS Emmet technology, press Tab to quickly generate code

✅ supports the introduction of external CDN styles and JS.

✅ add SCSS parsing module (based on online conversion API:sassmeister.com)

🌟 Main Principles

Use Codemirror to build three code block compilers of HTML, CSS and JS, and build an Iframe web page to display the effect. Postmessage is then used to pass code data to Iframe and replace the old code. At the same time, jsCDN and CssCDN paths can be passed in by using new or updated dynamic labels. Note that to avoid wasting memory by constantly changing the DOM, we can use something like debounce to refresh the code after it has stopped editing for a certain amount of time.

By simply rewriting HTML/CSS/JS to achieve the page update effect without refreshing the page.

function loadPage (htmlCode, cssCode, jsCode) {
  const _html = document.querySelector('#customHTML')
  if (_html) document.body.removeChild(_html)
  const html = document.createElement('div')
  html.id = 'customHTML'
  html.innerHTML = htmlCode
  document.body.appendChild(html)

  const _css = document.querySelector('#customCSS')
  if (_css) document.head.removeChild(_css)
  const css = document.createElement('style')
  css.id = 'customCSS'
  css.innerHTML = cssCode
  document.head.appendChild(css)

  const _script = document.querySelector('#customJS')
  if (_script) document.body.removeChild(_script)
  const script = document.createElement('script')
  script.id = 'customJS'
  script.innerHTML = jsCode
  document.body.appendChild(script)
}
Copy the code

Of course, there are some scenarios where you need to manually refresh the page, such as adding JS listeners and updating CDN paths. Update the timestamp for the iframe to refresh the page.

async function sendMessage(refresh = false) {
  if (refresh) {
    state.iframeURL.value = `./iframe.html? t=The ${+new Date()}`
    await new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(1)},2000)})}... }Copy the code

💻 Emmet

Emmet technology allows developers to sketch HTML code. Official documentation: docs.emmet.io/

Emmet has an NPM package that lets us use its API in the browser.

import expand from 'emmet'
// Input word and mode to get the sketched code
const result = expand(word, { type: mode }) 
Copy the code

At the same time, it should be noted that when writing the code, we need to extract the words before the sketch, we can use the space as the boundary to cut, refer to the following function

function getWord (line: string, ch: number) :string.number] {
  const getNearTagChar = (str: string) :string= > {
    for (let i = str.length - 1; i > 0; i--) {
      if (str[i] === '>' || str[i] === '<') return str[i]
    }
    return str[0] | |'<'
  }
  // The cursor is at the end of the line or word
  if (ch === line.length || (line.length > ch + 1 && (/\s/.test(line[ch]) || line[ch] === '<'))) {
    let i
    for (i = ch - 1; i >= 0; i--) {
      if (/\s/.test(line[i]) || (line[i] === '>' && getNearTagChar(line.slice(0, i)) === '<')) {
        break}}return [line.slice(i + 1, ch), i + 1]}return [' '.0]}Copy the code

When we input keywords, press Tab to quickly match the corresponding code block, currently support HTML and CSS sketch.

🗳 supports writing SASS code

Switching to the SCSS precompiler is supported in the project to generate CSS styles.

Since SASS is generally based on Node-Sass or Dart-Sass, which can be run in a browser using some techniques but require a large file size, only the online API is used. If Intranet deployment requirements can be further studied.

SASS to CSS address: sassmeister.com

export async function scss2css (scss) {
  try {
    const res = await fetch('https://api.sassmeister.com/compile', {
      method: 'POST'.body: JSON.stringify({
        input: scss,
        outputStyle: 'expanded'.syntax: 'SCSS'.compiler: 'dart - sass / 1.26.11'
      }),
      headers: {
        'Content-Type': 'application/json'}})if (res.status === 200) {
      return await res.json()
    } else {
      const json = await res.json()
      return Promise.reject(json)
    }
  } catch (e) {
    return Promise.reject(e)
  }
}
Copy the code

⚙Webpack configuration related

  • As the iframe page is generated, the project becomes a multi-page application that requires setting up two entries
  • Define SCSS variables for global color matching. In order to eliminate manual import, global injection is used when using CSS-Loader
  • Configure the CDN path to reduce the bandwidth pressure on the site (but due to codemirror internal construction issues, only CSS files are placed in the CDN this time)
// vue.config.js
const isProduction = process.env.NODE_ENV === 'production'
const assetsCDN = {
  css: [
    'https://cdn.bootcdn.net/ajax/libs/codemirror/5.58.1/codemirror.min.css'.'https://cdn.bootcdn.net/ajax/libs/codemirror/5.58.1/theme/material-darker.min.css'].js: []}const isHashMode = process.env.VUE_APP_ROUTER_MODE === 'hash'
const publicPath = isHashMode ? '/' : '/coder'
module.exports = {
  pages: {
    index: 'src/main.ts'.iframe: 'src/iframe.ts'
  },
  chainWebpack: config= > {
    config.plugin('html-index').tap(args= > {
      args[0].cdn = assetsCDN
      return args
    })
  },
  css: {
    loaderOptions: {
      scss: {
        prependData: '@import "~@/assets/variable.scss"; '}}},productionSourceMap: !isProduction,
  publicPath
}
Copy the code

📱 Compatible mobile mode (responsive design)

The code editor layout in the project supports custom stretching and folding on the PC side, using the author’s open source plug-in @howdyjs/ Resize.

CSS3 media query is generally used to achieve the responsive design. However, due to the layout problem, stretching layout is not suitable for mobile terminal, and an additional TAB page under mobile terminal is needed, so JS is needed for auxiliary implementation this time.

💡 automated build with Github Workflows

Since the project is purely front-end and can be packaged and deployed directly, it is a good fit to deploy using github Page.

Using Github workflows, you can build tasks that are packaged automatically when code is pushed and deployed to the Github Page branch.

There are already a lot of mature common tasks in the community that you don’t need to write yourself. Github deploy-pages action is used to publish code directly to the Github Page branch.

# .github/workflows/main.deploy.yml
name: Deploy Doc Website
on:
  push:
    branches:
      - main
jobs:
  main-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          persist-credentials: false
      - name: Setup node
        uses: actions/setup-node@v2
      - name: Install dependencies
        run: yarn
      - name: Build Demo
        run: yarn build:hash
      - name: Deploy
        uses: JamesIves/[email protected]
        with:
          GITHUB_TOKEN: The ${{ secrets.GITHUB_TOKEN }}
          BRANCH: gh-pages
          FOLDER: dist

Copy the code

Note that github page does not support the history routing mode

✏ Todo

😴 Javascript Babel mode 😴 Introduces account system synchronization code 😴 online code display mode