Let’s take a look at the tinymce class, which is updated to tinymce-plugins for the rest of the series

Tinymce is a rich text plug-in. The official website has been introduced very clearly

The main purpose of this series is to document the tinymce bugs and plugin development process at work (after all, there is so little information on the web…).

Tinymce environment setup

Tinymce is very simple to use, and the website explains it in detail (I won’t go into details about some simple and unnecessary configurations).

The environment setup here is not a tinymce source environment or simply run Tinymce, but to develop tinymce plug-in prepared environment. So let’s start with the environment setup

I’ve tried webPack before. Failed, perhaps is the strength is not enough ~

So the following will introduce rollup to build: rollup source document, rollup Chinese document

The next version to use is [email protected]

Note that the dev version is downloaded, as you will need to see the source code later

Initialize the directory structure

After downloading the Dev code, the source code is stored in the Modules folder. The JS to build the environment is hidden deep, so find the folder with tinymce.min.js in the upper layer. That’s the JS we’re going to introduce

The structure of the directory is as follows: the code for Tinymce is in the red box, and the rest is to create our own index.html

  • Index.html content

If you need to configure language, you need to download the corresponding Chinese package, I will not care about it here.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Tinymce plug-in development environment</title>
    <script src="./js/tinymce/tinymce.min.js"></script>
  </head>

  <body>
    <textarea class="tinymce" style="height:90vh;" id="tinymce"> </textarea>
  </body>

  <script>
    tinymce.init({
      selector: 'textarea.tinymce'.plugins: 'code advlist lists advlist'.toolbar: 'undo redo | advlist fontsizeselect lineheight'.setup(editor){}})</script>
</html>
Copy the code

If you open index.html or run http-server, you can see the following:

Run with Rollup, package

Webpack and Rollup are both packaging tools, and packaging configurations is not difficult. But every time after watching the tutorial packaging will step on the pit, that is because each period depends on the package version problem = =. That’s the biggest obstacle to packing

Add 2 run commands to run and package.

Rollup can be packaged without a configuration file

Here I will only use -c and — Watch functions in the serve environment.

-c You can specify a configuration file entry. — Watch listens for file changes

From running the command, we can see that we are going to build a build/rollup.config.js

To reduce the stomping process, directly post my dependency package configuration (especially Babel)

{
  "scripts": {
    "serve": "cross-env NODE_ENV=development rollup -c build/rollup.config.js --watch"."build": "cross-env NODE_ENV=production rollup -c build/rollup.config.js"
  },

  "devDependencies": {
    "babel-preset-es2015-rollup": "^ 3.0.0"."babel-preset-stage-0": "^ 6.22.0"."cross-env": "^ 7.0.3." "."less": "^ 4.1.2." "."livereload": "^ 0.9.3"."postcss": "^ 8.3.9"."rollup": "^ 2.58.0"."rollup-plugin-babel": "^ 2.7.1." "."rollup-plugin-clear": "^ 2.0.7." "."rollup-plugin-commonjs": "^ 10.1.0"."rollup-plugin-copy": "^ 3.4.0"."rollup-plugin-livereload": "^ at 2.0.5." "."rollup-plugin-postcss": "^ 4.0.1." "."rollup-plugin-serve": "^ 1.1.0." "."rollup-plugin-uglify": "^ 6.0.4"."rollup-watch": "^ 3.2.2." "}}Copy the code

Rollup package effect

What does this configuration accomplish?

  • Since we may develop more than one plug-in, there will beMultiple entrances and multiple exitsPackaging requirements of
  • Tinymce formal environment loading is.min.jsFile. So there has to be a compression JS function
  • Many plugins may not necessarily meet our style requirements, but still need to support their use.less
  • Turn ES6 ES5
  • Support server, open localhost:8080 after running
  • Support for hot updates
  • .

Create 2 new plug-ins and write rollup.config.js

According to the tinymce plugin directory requirements

  1. Plug-ins are injs/tinymce/plugins/
  2. Name the folder with the plug-in name
  3. It is available in the corresponding plug-in folderplugin.jsplugin.min.js. Tinymce automatically selects whether to load.js or.min.js.

So the test plug-in directory is as follows:

Use this to open index.html. To initialize tinymce, add myplugins myplugins2 to your plugins:

tinymce.init({
  selector: 'textarea.tinymce'.plugins: 'code advlist lists advlist myplugins myplugins2'.toolbar: 'undo redo | advlist fontsizeselect lineheight'
})
Copy the code

See that the plug-in is loading properly

Then there is the rollup.config.js configuration

  • build/rollup.config.js

Configuration prototype, because it is multi-entry multi-exit, so can only export an array of configuration

Format, just to mention, is packaged as iife, which is to execute functions immediately. Other output types :(amd, CJS, esm, iife, umd)

And of course there are other configurations,

const path = require('path')
const pluginsPath = path.resolve(__dirname, '.. /src/js/tinymce/plugins/')

export default[{input: path.join(pluginsPath, 'myplugins/plugin.js'),
    output: {
      format: 'iife'.file: path.join(pluginsPath, 'myplugins/plugin.min.js')}}, {input: path.join(pluginsPath, 'myplugins2/plugin.js'),
    output: {
      format: 'iife'.file: path.join(pluginsPath, 'myplugins2/plugin.min.js')}}]Copy the code

Then run NPM Run Serve to start the configuration. See the following effect is in effect

Rollup Transfers ES6 to ES5

The above effect shows that format is in effect, packaging our function as an immediate function. But it’s really just packaged as an immediate function that doesn’t convert if we write some ES6 syntax

Next, I will use 2 libraries (if package.json has been directly configured like mine, there is no need to install it again).

  • Rollup-plugin – Babel ES6 to ES5 plugin

  • babel-preset-stage-0

  • babel-preset-es2015-rollup

  • Rollup-plugin-commonjs converts CommonJS modules to ES6 to prevent them from failing in rollup;

import babel from 'rollup-plugin-babel' // ES6 to ES5 plug-in
import commonjs from 'rollup-plugin-commonjs' // Convert CommonJS modules to ES6 to prevent them from failing in Rollup;

export default[{input: path.join(pluginsPath, 'myplugins/plugin.js'),
    output: {
      format: 'iife'.file: path.join(pluginsPath, 'myplugins/plugin.min.js')},plugins: [
      // Add the plug-in configuration
      commonjs(), / / es6
      babel({
        babelrc: false.// do not set. Babelrc file;
        exclude: 'node_modules/**'.// Ignore the node_modules directory (although we don't use node_modules either)
        presets: ['es2015-rollup'.'stage-0'].// Convert ES5 plugins;
        plugins: ['transform-class-properties'] // Convert static class attributes and attribute initialization syntax for attribute declarations}})]/ /...
]
Copy the code

Rerun and find that the syntax has been escaped

Rollup configures code compression

Since it is.min.js that compression is indispensable

The plug-in package involved is rollup-plugin-uglify

import { uglify } from 'rollup-plugin-uglify' / / js compressed
export default[{// ...
    plugins: [
      // ...
      uglify()
    ]
  }
]
Copy the code

Does the configuration feel particularly simple? ! Just import the plug-in and execute the method in the plugins directory

Rollup-plugin-uglify import uglify from ‘rollup-plugin-uglify’ import uglify from ‘rollup-plugin-uglify’ If I’m using my version, I need to destruct the assignment to introduce it.

Rerun it and you’ll see what happens

Add serve and hot update

  • rollup-plugin-serve
  • rollup-plugin-livereload
  • livereload

Note that some packages are not necessarily imported, such as livereload, but rollup-plugin-livereload relies on this package, so it is installed

For the configuration of serve, see npm-rollup-plugin-server

import serve from 'rollup-plugin-serve' / / serve service
import livereload from 'rollup-plugin-livereload' / / hot update

// Introduce a new variable
const srcPath = path.resolve(__dirname, '.. /src/')

export default[{// ...
    plugins: [
      // ...
      serve({
        open: true.// Automatically open the browser
        verbose: true.// Outputs the open link at the terminal
        contentBase: srcPath, // Project entry
        openPage: '/index.html'.// Open the page by default
        port: '8080' / / the port number
      }),
      livereload()
    ]
  },
  {
    // ...
    plugins: [livereload()] // Serve only needs to be executed once}]Copy the code

Server only needs to execute once, and livereload needs to execute as many times as many entries

Because server starts one, and multiple entries listen for file changes, hot updates rely on livereload’s plugin

If you see the browser automatically open, you’re configured correctly!

Take a look at plugins.min.js. The plugin added a hot update code for us.

The hot update effect is forced refresh rather than partial update, but it is also very fragrant

A rollup support less

Don’t ask why SCSS is not used, in fact, is the same reason

Required plug-in packages:

  • less
  • postcss
  • rollup-plugin-postcss
import postcss from 'rollup-plugin-postcss'

export default[{plugins: [
      postcss({
        extensions: ['.less'].// Compile.less files
        extract: true.modules: false.inject: false.// Core, do not insert CSS code in HTML, because Tinymce has its own way of introducing CSS
        minimize: true
      })
      // ...]}]Copy the code

Notice the order of introduction

If we process JS first and then CSS, we get an error. Therefore, we should refer to plugins first when dealing with less

Another thing to note is that the less file must be introduced in plugin.js, otherwise rollup will not package the CSS file for you

The second is that it doesn’t matter how many less files you have, because the output name of the output configuration is plugin.min.js. So CSS will eventually be incorporated into plugin.min.css

Less import ‘./style/index.less’ in plugin.js and see plugin.min.css output from the same directory

Packaging configuration

At this point, we have the basic skeleton. However, the above configuration is to run serve. We are packing the official package, we definitely can’t have the code like hot update, so we are making some changes for NPM run build

  • Cross-env sets environment variables
  • Rollup-plugin-clear Clears the package directory

Cross-env sets NODE_ENV in package.json

So we decide it’s the development environment/packaging just need to decide

const isDev = process.env.NODE_ENV == 'development'
Copy the code

In the development directory/SRC /js/tinymce/plugins there may be a lot of irrelevant files, so we also need to package a copy to the /dist directory so that we can release the version

For a file with multiple exits, output can be configured as an array!

Of course, this part is just pseudo code, also need to comment out serve

const rootPath = path.resolve(__dirname, '.. / ')
const pluginsPath = path.resolve(__dirname, '.. /src/js/tinymce/plugins/')

const isDev = process.env.NODE_ENV == 'development'

export default[{input: path.join(pluginsPath, 'myplugins/plugin.js'),
    output: [{format: 'iife'.file: path.join(pluginsPath, 'myplugins/plugin.min.js')}, {format: 'iife'.file: path.join(rootPath, 'dist/myplugins/plugin.min.js')}, {format: 'iife'.file: path.join(rootPath, 'dist/myplugins/plugin.js'}]}]Copy the code

The package looks like this (there are a few minor problems, the style is packed a lot, plugin.js is not uncompressed code, the configuration will continue to improve) :

Dynamic entrance

Dynamic entry is simply specifying a directory, traversing the folder, and generating the corresponding input and output according to our rules

Including some of the previous code is also reorganized into the following:

  • build/getEntry.js
const path = require('path')
const fs = require('fs')

module.exports = function(entry) {
  if(! entry) {console.log('Entry directory does not exist', entry)
    process.exit(0)}try {
    let stat = fs.statSync(entry)

    if(! stat.isDirectory()) {console.log('Entry directory does not exist', entry)
      process.exit(0)}}catch (e) {
    console.log('Entry directory does not exist', entry)
    process.exit(0)}let routes = []

  fs.readdirSync(entry).forEach(function(item, index) {
    let pluginFolder = path.join(entry, item)
    let floderStat = fs.statSync(pluginFolder)

    // Check whether the plugin directory is available
    if (floderStat.isDirectory()) {
      try {
        let plugin = path.join(pluginFolder, 'plugin.js')
        let pluginStat = fs.statSync(plugin)
        if (pluginStat && pluginStat.isFile()) {
          routes.push({
            path: plugin,
            floder: pluginFolder,
            name: item
          })
        }
      } catch (e) {}
    }
  })

  return routes
}
Copy the code
  • build/getPlugins.js
const babel = require('rollup-plugin-babel') // ES6 to ES5 plug-in
const commonjs = require('rollup-plugin-commonjs') // Convert CommonJS modules to ES6 to prevent them from failing in Rollup;
const { uglify } = require('rollup-plugin-uglify') / / js compressed
const serve = require('rollup-plugin-serve') / / serve service
const livereload = require('rollup-plugin-livereload') / / hot update
const postcss = require('rollup-plugin-postcss')

/** * Get plugins for different configurations *@param {*} IsDev Whether the development environment *@param {*} IsFirst is the first entry *@param {*} Config Extended configuration *@returns* /
module.exports = function(isDev, isFirst, config = {}) {
  let plugins = [
    postcss({
      extensions: ['.less'].// Compile.less files
      extract: true.modules: false.inject: false.// Core, do not insert CSS code in HTML, because Tinymce has its own way of introducing CSS
      minimize: !config.unUglify
    }),
    commonjs(),
    babel({
      babelrc: false.// do not set. Babelrc file;
      exclude: 'node_modules/**'.presets: ['es2015-rollup'.'stage-0'].// Convert ES5 plugins;
      plugins: ['transform-class-properties'] // Convert static class attributes and attribute initialization syntax for attribute declarations})]if (isDev) {
    // Only the first one needs to serve
    isFirst &&
      plugins.push(
        serve({
          open: true.// Automatically open the browser
          verbose: true.// Outputs the open link at the terminal
          contentBase: config.srcPath || ' '.// Project entry
          openPage: '/index.html'.// Open the page by default
          port: '8080' / / the port number}))// dev are heated and updated
    plugins.push(livereload())
  } else if(! config.unUglify) {// Package to compress js
    plugins.push(uglify())
  }

  return plugins
}
Copy the code
  • build/rollup.config.js
const path = require('path')

const rootPath = path.resolve(__dirname, '.. / ')
const pluginsPath = path.resolve(__dirname, '.. /src/js/tinymce/plugins/')
const srcPath = path.resolve(__dirname, '.. /src/')

const isDev = process.env.NODE_ENV == 'development'

const getEntry = require('./getEntry')
const getPlugins = require('./getPlugins')

function run() {
  let config = []
  let isFirst = true
  getEntry(pluginsPath).forEach(item= > {
    let _output = [
      {
        format: 'iife'.file: path.join(item.floder, 'plugin.min.js') // Base directory, packaged in plugins}]if(! isDev) { _output.push({format: 'iife'.file: path.join(rootPath, 'dist', item.name, 'plugin.min.js') // Package directory
      })
    }

    config.push({
      input: item.path,
      output: _output,
      plugins: getPlugins(isDev, isFirst, { srcPath }) }) ! isDev && config.push({input: item.path,
        output: {
          format: 'iife'.file: path.join(rootPath, 'dist', item.name, 'plugin.js') // Package an uncompressed
        },
        plugins: getPlugins(isDev, isFirst, { srcPath, unUglify: true })
      })

    isFirst = false
  })

  return config
}

export default run()
Copy the code

Package by directory

If we do not differentiate directories, then plugins built into plugins are also our packaging objects. Obviously, we do not care about this part of the code, so we add a configuration to filter out this part of the file

  • Method 1: Write a configuration file and pack only the plug-in names you need
  • Method 2: Standardize the naming of plug-ins, such as adding a single my_ to distinguish them

So I went to build/ getentry.js instead

If you change the prefix name, remember to change the relevant introduction ~

Introduce static resources and clean up old resources

Finally, we found a small problem that the packaging of less did not help us package the image resources

Therefore, an agreement is made: the assets directory under the plug-in. Although it does not participate in packaging, a copy of the assets directory is directly made during packaging. At this time, another problem is the packaged less, the hierarchy does not automatically help us to correct, such as the following situation:

In this case, the value can only be greater than the value configured. After the document is written, the CSS files are written to the root directory of the plug-in, and the corresponding assets are imported. Otherwise the situation is too extreme (lazy)

The plug-in package to use

  • rollup-plugin-clear

  • rollup-plugin-copy

  • Modify build/ getentry. js to check whether assets need to be copied to the directory

  • Change build/getPlugins.js to introduce clear and copy plugins

I won’t post the code, but look at gitee’s tinymce-plugins

The last

At this point tinymCE series of the first chapter tinymCE environment build, set up

The tinymce plug-in is also based on this environment