当前位置:   article > 正文

tinymce系列(一) tinymce 环境搭建_引入 tinymce 为什么只有一个 textarea

引入 tinymce 为什么只有一个 textarea

先介绍一下这个系类(tinymce),该系列以后相关的代码都会更新在 tinymce-plugins

tinymce 是一个富文本插件。官网已经介绍的很清楚了

这个系列主要的目的就是把在工作中遇到的 tinymce 遇到的坑和一些插件开发流程记录下来(毕竟网上这部分资料真的太少了。。。)

tinymce 环境搭建

tinymce 使用是非常简单的,官网也有很详细的介绍(一些简单非必要的配置我就不细说了)

这里的环境搭建并不是 tinymce 的源码环境或者只是单纯运行 tinymce,而是为了开发 tinymce 配套插件准备的环境。所以就从环境搭建先开始吧

之前尝试过用 webpack。失败了,或许是实力不够把~

所以下面会介绍使用 rollup 来搭建:rollup 源文档rollup 中文文档

接下来使用的版本是 tinymce@5.2.0

注意下载的是 dev 版本,因为后续有看源码的需要~

初始化目录结构

下载了 Dev 的代码后,源码部分都在 modules 文件夹里面的,搭建环境要用的 JS 隐藏的比较深,找到有 tinymce.min.js 的文件夹的上一层。就是我们要引入的 JS 了

目录结构如下:红色框中的就是 tinymce 的代码,剩下的我们自己新建一个 index.html

  • index.html 内容

如果需要配置 language ,需要自己下载对应的中文包,我这里就不管他了。

<!DOCTYPE html>
<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 插件开发环境</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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这时候直接打开 index.html 或者用 http-server 运行可以看到如下:

使用 Rollup 运行,打包

webpack 和 rollup 都是打包工具,打包配置并不难。
可是每次看完教程自己打包都会踩坑,那是因为每个时期的依赖包版本的问题 = =。 这才是打包的最大障碍

添加 2 个运行命令,用于运行和打包的。

rollup 号称是无须配置文件都可以打包的,具体可以看下官网 rollup 命令

这里我就只在 serve 环境下使用 -c--watch 功能。

-c 可以指定配置文件入口。–watch 监听文件变化

从运行命令看得出,我们要建一个 build/rollup.config.js

为了减少踩坑过程,直接贴出我的依赖包配置(尤其是 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": "^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"
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

Rollup 打包效果

这个配置要达到什么效果?

  • 因为后续我们开发的插件可能不止一款,那么就会有多入口多出口的打包需求
  • tinymce 正式环境加载的都是 .min.js 文件。所以要有一个压缩 JS 的功能
  • 很多插件有可能不一定满足我们样式需求,还得支持使用 .less
  • ES6 转 ES5
  • 支持 server,运行后打开 localhost:8080
  • 支持热更新

新建 2 个插件,编写 rollup.config.js

根据 tinymce 的插件目录要求

  1. 插件都在 js/tinymce/plugins/
  2. 以插件名命名文件夹
  3. 对应插件文件夹下有 plugin.jsplugin.min.js。tinymce 会自动选择是加载 .js 或 .min.js。

所以测试插件目录如下:

使用的话打开 index.html。在初始化 tinymce 的代码中改为如下(在 plugins 加入 myplugins myplugins2):

tinymce.init({
  selector: 'textarea.tinymce',
  plugins: 'code advlist lists advlist myplugins myplugins2',
  toolbar: 'undo redo | advlist fontsizeselect lineheight'
})
  • 1
  • 2
  • 3
  • 4
  • 5

看到插件有正常加载

然后就是 rollup.config.js 的配置了

  • build/rollup.config.js

配置的雏形,因为是多入口多出口,所以只能导出一个数组的配置了

format 需要提一下就是打包为iife ,就是立即执行函数。等下就能看到效果了
其他输出的类型:(amd, cjs, esm, iife, umd)

当然还有其他的配置,

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')
    }
  }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

然后运行npm run serve,来启动这份配置。看到下面的效果就是生效了

rollup 配置 ES6 转 ES5

上面的效果可以看出 format 已经生效了,把我们的函数打包为立即执行函数。但也真的仅仅是打包为立即执行函数,如果我们写一些 ES6 语法,并不会转换

接下来用到 2 个库(如果上面和我一样已经直接配置好 package.json 的就不用重复安装了)

  • rollup-plugin-babel ES6 转 ES5 插件

  • babel-preset-stage-0

  • babel-preset-es2015-rollup

  • rollup-plugin-commonjs 将 CommonJS 模块转换成 ES6,防止他们在 Rollup 中失效;

import babel from 'rollup-plugin-babel' // ES6转ES5插件
import commonjs from 'rollup-plugin-commonjs' // 将CommonJS模块转换成ES6,防止他们在Rollup中失效;

export default [
  {
    input: path.join(pluginsPath, 'myplugins/plugin.js'),
    output: {
      format: 'iife',
      file: path.join(pluginsPath, 'myplugins/plugin.min.js')
    },
    plugins: [
      // 添加插件配置
      commonjs(), // es6 转
      babel({
        babelrc: false, //不设置.babelrc文件;
        exclude: 'node_modules/**', // 忽略 node_modules 目录(虽然我们也用不上 node_modules)
        presets: ['es2015-rollup', 'stage-0'], //转ES5的插件;
        plugins: ['transform-class-properties'] //转换静态类属性以及属性声明的属性初始化语法
      })
    ]
  }
  // ....
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

重新运行,发现语法已经有转义了

rollup 配置 代码压缩

既然是 .min.js 那压缩少不了

涉及的插件包就是 rollup-plugin-uglify

import { uglify } from 'rollup-plugin-uglify' // js压缩
export default [
  {
    // ...
    plugins: [
      // ...
      uglify()
    ]
  }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

是不是感觉配置特别简单?!就引入插件,在 plugins 目录执行方法就行了

rollup-plugin-uglify 注意下版本的问题,就版本引入是 import uglify from 'rollup-plugin-uglify'。如果用的是我的版本的话,需要通过解构赋值来引入。

重新运行就能看到效果了

添加 serve 和 热更新

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

注意有些包不一定是要引入的,比如 livereload 就不需要引入,不过 rollup-plugin-livereload 依赖了这个包,所以才需要装

关于 serve 的配置,可以看下: npm - rollup-plugin-server

import serve from 'rollup-plugin-serve' // serve服务
import livereload from 'rollup-plugin-livereload' // 热更新

// 引入一个新变量
const srcPath = path.resolve(__dirname, '../src/')

export default [
  {
    // ...
    plugins: [
      // ...
      serve({
        open: true, // 自动打开浏览器
        verbose: true, // 在终端输出打开的链接
        contentBase: srcPath, // 项目入口
        openPage: '/index.html', // 默认打开的页面
        port: '8080' // 端口号
      }),
      livereload()
    ]
  },
  {
    // ...
    plugins: [livereload()] // 注意 serve 只要执行一次
  }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

server 只需要执行一次,livereload 需要有多少个入口执行多少次

因为 server 启动一个就够了,而多入口监听文件变化,热更新是依赖 livereload 的插件的

看到有自动打开浏览器,那就是配置对了!

看下 plugins.min.js 文件,插件帮我们加了一份热更新代码。

热更新效果 虽然是强制刷新而不是局部更新,可是那也很香了好吗~

rollup 支持 less

不要问为什么不用 scss,其实都是一个道理的

需要的插件包:

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

export default [
  {
    plugins: [
      postcss({
        extensions: ['.less'], // 编译.less 文件
        extract: true,
        modules: false,
        inject: false, // 核心,不要在 html 插入css代码,因为 tinymce 有自己引入css的一套方法
        minimize: true
      })
      // ...
    ]
  }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注意引入顺序

如果我们先处理 js ,然后在处理 css 就会报错。所以我们处理 less 应该提到 plugins 的第一位

还有一个要注意点的是,less 文件必须在 plugin.js 引入,否则 rollup 是不会帮你打包对应的 css 文件的

其次就是,不管你的 less 文件有多少个,因为 output 配置的输出名称是是 plugin.min.js。所以 css 最后都会被合并到 plugin.min.css

可以看到现在是写了 .less 文件完全没反应的,接下来在 plugin.js 引入一下 less import './style/index.less' 就能看到同级目录输出 plugin.min.css

打包配置

至此,基本骨架已经有了。不过上面的配置都是运行 serve 的。我们打包正式的包,肯定不能有热更新之类的代码了,所以针对 npm run build 在做一些修改

  • cross-env 设置环境变量
  • rollup-plugin-clear 清理打包目录

cross-env 在 package.json 文件就有设置了 NODE_ENV

所以我们判断是开发环境/打包只需要判断

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

在开发目录 /src/js/tinymce/plugins 里面可能会有很多无关的文件,所以打包我们还需要打包一份到 /dist 目录,方便我们发布版本

对于这种一个文件对应多个出口的,output 支持配置为数组!

当然了,这部分只是伪代码,还需要把 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')
      }
    ]
  }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

打包后的效果如下(有几个小问题,样式打包多了,plugin.js 并非未压缩的代码,后面的配置还会继续完善):

动态入口

动态入口其实就是指定目录后,遍历文件夹,然后根据我们的规则生成对应的 inputoutput 就行了

包括之前的一些代码也重新整理下,整理为如下几个:

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

module.exports = function(entry) {
  if (!entry) {
    console.log('入口目录不存在', entry)
    process.exit(0)
  }

  try {
    let stat = fs.statSync(entry)

    if (!stat.isDirectory()) {
      console.log('入口目录不存在', entry)
      process.exit(0)
    }
  } catch (e) {
    console.log('入口目录不存在', entry)
    process.exit(0)
  }

  let routes = []

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

    // 这里判断是否插件目录
    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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • build/getPlugins.js
const babel = require('rollup-plugin-babel') // ES6转ES5插件
const commonjs = require('rollup-plugin-commonjs') // 将CommonJS模块转换成ES6,防止他们在Rollup中失效;
const { uglify } = require('rollup-plugin-uglify') // js压缩
const serve = require('rollup-plugin-serve') // serve服务
const livereload = require('rollup-plugin-livereload') // 热更新
const postcss = require('rollup-plugin-postcss')

/**
 * 获取不同配置对应的插件
 * @param {*} isDev 是否开发环境
 * @param {*} isFirst 是否第一个入口
 * @param {*} config 拓展配置
 * @returns
 */
module.exports = function(isDev, isFirst, config = {}) {
  let plugins = [
    postcss({
      extensions: ['.less'], // 编译.less 文件
      extract: true,
      modules: false,
      inject: false, // 核心,不要在 html 插入css代码,因为 tinymce 有自己引入css的一套方法
      minimize: !config.unUglify
    }),
    commonjs(),
    babel({
      babelrc: false, //不设置.babelrc文件;
      exclude: 'node_modules/**',
      presets: ['es2015-rollup', 'stage-0'], //转ES5的插件;
      plugins: ['transform-class-properties'] //转换静态类属性以及属性声明的属性初始化语法
    })
  ]

  if (isDev) {
    // 只有第一个才需要serve
    isFirst &&
      plugins.push(
        serve({
          open: true, // 自动打开浏览器
          verbose: true, // 在终端输出打开的链接
          contentBase: config.srcPath || '', // 项目入口
          openPage: '/index.html', // 默认打开的页面
          port: '8080' // 端口号
        })
      )

    // dev 都加热更新
    plugins.push(livereload())
  } else if (!config.unUglify) {
    // 打包才压缩js
    plugins.push(uglify())
  }

  return plugins
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 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') // 基础目录,打包在 plugins
      }
    ]
    if (!isDev) {
      _output.push({
        format: 'iife',
        file: path.join(rootPath, 'dist', item.name, 'plugin.min.js') // 打包目录
      })
    }

    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') // 打包一个不压缩的
        },
        plugins: getPlugins(isDev, isFirst, { srcPath, unUglify: true })
      })

    isFirst = false
  })

  return config
}

export default run()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

根据目录打包

如果不区分目录,那么 plugins 下内置的插件也是我们的打包对象,显然我们并不用关心这部分代码,所以加一个配置来过滤掉这部分的文件

  • 方法 1 : 写一个配置文件,打包只需要的插件名称
  • 方法 2 : 规范插件命名方式,比如统一加一个 my_ 来区分

所以我采用了方式 2,改一下 build/getEntry.js

改了前缀名的话,相关的引入记得也改一下~

引入静态资源 和 清理旧资源

最后发现一个小问题就是打包 less 的时候并没有帮我们把图片资源打包进入

所以在约定一个:插件下的 assets 目录,虽然不参与打包,但是打包的时候直接把 assets 目录复制一份。这时候引发另外一个问题就是打包后的 less,层级没有自动帮我们改过来,比如下面的情况:

这种情况,只能约定大于配置了~写好文档,css 文件都写插件根目录,然后引入对应 assets 资源。不然这种情况太极端了 (就是懒)

要用到的插件包

  • rollup-plugin-clear

  • rollup-plugin-copy

  • 修改 build/getEntry.js 判断目录是否需要复制 assets

  • 修改 build/getPlugins.js 引入 clear 和 copy 插件

代码就不贴了,看下 gitee 把 tinymce-plugins

最后

至此 tinymce 系列的第一章 tinymce 环境搭建,就搭建好了

以后开发 tinymce 插件也是基于这个环境了~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/733884
推荐阅读
相关标签
  

闽ICP备14008679号