15

I'm trying to get tinymce recognized by webpack. It sets a property named tinymce on window, so evidently one option is to require() it using syntax like this (described at the bottom of the EXPORTING section of the webpack docs):

require("imports?window=>{}!exports?window.XModule!./file.js

But in this example, how is ./file.js resolved? I installed tinymce via npm, and I can't figure out how to specify the right path to the tinymce.js file.

Regardless, I'd rather handle this in my configuration and be able to just require('tinymce') if possible, so I've installed exports-loader and added the following to my configuration (based on this discussion):

module: {
    loaders: [
        {
            test: /[\/]tinymce\.js$/,
            loader: 'exports?tinymce'
        }
    ]
}

Unfortunately this isn't working. What's wrong with my configuration?

Rob Johansen
  • 5,076
  • 10
  • 40
  • 72

6 Answers6

11

The tinymce module on npm can't be required directly, but contains 4 different distributions of the library. Namely:

  • tinymce/tinymce.js
  • tinymce/tinymce.min.js
  • tinymce/tinymce.jquery.js
  • tinymce/tinymce.jquery.min.js

To be able to do require('tinymce') in your code, you can add an alias in your webpack config, as well as a custom loader for your distribution of choice.

resolve: {
  alias: {
    // require('tinymce') will do require('tinymce/tinymce') 
    tinymce: 'tinymce/tinymce',
  },
},
module: {
  loaders: [
    {
      // Only apply on tinymce/tinymce
      include: require.resolve('tinymce/tinymce'),
      // Export window.tinymce
      loader: 'exports?window.tinymce',
    },
  ],
},

Where you can replace tinymce/tinymce with your distribution of choice.

Alexandre Kirszenberg
  • 35,938
  • 10
  • 88
  • 72
  • Thanks for the response. Now webpack is telling me `Cannot resolve module 'imports'` in the files where I `require('tinymce')`. I do have `imports-loader` installed, so I'm not sure what else could be wrong. – Rob Johansen May 29 '15 at 07:29
  • You might have to provide the full module names. `imports-loader?window=>{}!exports-loader?window.tinyMCE![dist]` – Alexandre Kirszenberg May 29 '15 at 08:10
  • Yes, the full name of `imports-loader` worked: `tinymce: 'imports-loader?window=>{}!exports?window.tinymce!tinymce/tinymce.js'` – Rob Johansen May 29 '15 at 08:18
  • Although I'm not having the build problem anymore, I discovered today that tinymce is being imported as a function, and that it doesn't have any of the methods I'm expecting (such is the `init()` method for actually creating a WYSIWYG instance). How can I tell webpack to import tinymce as an object and retain all of its methods? – Rob Johansen May 29 '15 at 21:44
  • Could have sworn `resolve.alias` values supported loaders, but it appears they don't (see [this issue](https://github.com/webpack/webpack/issues/268)). My previous suggestion caused `imports-loader` to be required on the client instead of tinymce. I have edited my answer. – Alexandre Kirszenberg May 30 '15 at 14:51
  • I kept getting errors, no matter which way I tried to shim. Ended up using the cdn. That works – Alex Oct 22 '15 at 09:02
11

Just like @cchamberlain I ended up using script loader for tinymce, but to load the plugins and other resources that were not required by default I used CopyWebpackPlugin instead of ES6 for more configurable solution.

var copyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
  plugins: [
    new copyWebpackPlugin([
      { from: './node_modules/tinymce/plugins', to: './plugins' },
      { from: './node_modules/tinymce/themes', to: './themes' },
      { from: './node_modules/tinymce/skins', to: './skins' }
    ])
  ]
};
Petri Ryhänen
  • 747
  • 1
  • 8
  • 10
4

I was able to integrate tinyMCE in my Angular 2/TypeScript based project by using the imports-loader and exports-loader and the copy-webpack-plugin.

First ensure that the necessary dependencies are available and part of the packages.json file of your project:

npm i tinymce --save
npm i exports-loader --save-dev
npm i imports-loader --save-dev    
npm i copy-webpack-plugin --save-dev

Then add the required loader to the loaders-section of your webpack configuration:

    loaders: [          
        {
            test: require.resolve('tinymce/tinymce'),
            loaders: [
                'imports?this=>window',
                'exports?window.tinymce'
            ]
        },
        {
            test: /tinymce\/(themes|plugins)\//,
            loaders: [
                'imports?this=>window'
            ]
        }]

To make the copyWebpackPlugin available in your webpack configuration, import it in the header part of the webpack configuration file:

var copyWebpackPlugin = require('copy-webpack-plugin');

And, as Petri Ryhänen commented, add the following entry to the plugins-section of your webpack configuration:

plugins: [
    new copyWebpackPlugin([
        { from: './node_modules/tinymce/plugins', to: './plugins' },
        { from: './node_modules/tinymce/themes', to: './themes' },
        { from: './node_modules/tinymce/skins', to: './skins' }
    ])
]

This step ensures that (required) addons of tinyMCE are also available in your webpack.

Finally to import tinyMCE in your Angular 2 component file, add

require('tinymce')

declare var tinymce: any;

to the import section and tinyMCE is ready to use.

  • Fantastic answer, it's working great. Note, [now](https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed) you need to write `'imports-loader?this=>window'`, and so on. – Drasill Nov 06 '18 at 14:09
3

I got this to work similar to how I bundle React to ensure I don't get two separate instances in DOM. I had some issues with imports / exports / expose loaders so instead I used script-loader.

In my setup I have a commons chunk that I use strictly for vendors (React / tinymce).

   entry: { 'loading': '../src/app/entry/loading'
          , 'app':  '../src/app/entry/app'
          , 'timeout': '../src/app/entry/timeout'
          , 'commons':  [ 'expose?React!react'
                        , 'expose?ReactDOM!react-dom'
                        , 'script!tinymce/tinymce.min.js'
                        ]
          }

This is working for me the same way that including the script from CDN would work however I now had errors because it could not find my themes / plugins / skins paths from my node_modules location. It was looking for them at paths /assets/plugins, /assets/themes, /assets/skins (I use webpack public path /assets/).

I resolved the second issue by mapping express to serve these two routes statically like so (es6):

  const NODE_MODULES_ROOT = path.resolve(__dirname, 'node_modules')
  const TINYMCE_PLUGINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/plugins')
  const TINYMCE_THEMES_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/themes')
  const TINYMCE_SKINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/skins')

  router.use('/assets/plugins', express.static(TINYMCE_PLUGINS_ROOT))
  router.use('/assets/themes', express.static(TINYMCE_THEMES_ROOT))
  router.use('/assets/skins', express.static(TINYMCE_SKINS_ROOT))

After doing this window.tinymce / window.tinyMCE are both defined and functions same as CDN.

cchamberlain
  • 17,444
  • 7
  • 59
  • 72
1

As an addition to this answer (thanks to Petri Ryhänen), I want to add my copyWebpackPlugin and tinymce.init() configuration adjustments.

new copyWebpackPlugin([{
  context: './node_modules/tinymce/skins/lightgray',
  from: './**/*',
  to: './tinymce/skin',
}]),

With this configuration you will get all skin files in {output}/tinymce/skin folder.

Then you can initialize tinymce like this:

import tinymce from 'tinymce/tinymce';

// A theme is also required
import 'tinymce/themes/modern/theme';  // you may change to 'inlite' theme

// Any plugins you want to use has to be imported
import 'tinymce/plugins/advlist/plugin';
// ... possibly other plugins

// Then anywhere in this file you can:
tinymce.init({
  // ... possibly other options
  skin_url: '/tinymce/skin',  // <-- !!! here we tell tinymce where
                              //         to load skin files from
  // ... possibly other options
});

With this I have both development and production builds working normally.

Ruslan Zhomir
  • 842
  • 9
  • 27
0

We use TinyMCE jQuery 4.1.6 and the accepted answer did not work for us because window seems to be used in other locations by TinyMCE (e.g. window.setTimeout). Also, document not being shimmed seemed to cause problems.

This works for us:

alias: {
    'tinymce': 'tinymce/tinymce.jquery.js'
}

module: {
    loaders: [
        {
            test: /tinymce\/tinymce\.jquery\.js/,
            loader: 'imports?document=>window.document,this=>window!exports?window.tinymce'
        }
    ]
}

Load your plugins like this:

{
    test:  /tinymce\/plugins/,
    loader: 'imports?tinymce,this=>{tinymce:tinymce}'
}
cjhveal
  • 5,668
  • 2
  • 28
  • 38
Joscha
  • 4,643
  • 1
  • 27
  • 34
  • I am not able to call the tinymce function on jQuery objects when using webpack. tinyMCE and tinymce are bound to window. I receive `$(...).tinymce is not a function` when trying to call tinymce on a jQuery object. – Chris McKnight Jun 08 '15 at 16:50