Codemirror - Module parse failed: Unexpected token

639 Views Asked by At

I am using react-codemirror2. I used npx create-react-app appname to create my app.

But when I try to run the development server it gives me the following error -

./node_modules/codemirror/mode/rpm/changes/index.html 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
> <!doctype html>
| 
| <title>CodeMirror: RPM changes mode</title>

One solution suggested to change the modulesDirectories. I tried doing so using npm run eject. But wasn't successful at doing it.

Please help me with the same

2

There are 2 best solutions below

1
On

The doctype declaration is incorrect, it should have been:

<!DOCTYPE html>

Notice DOCTYPE should be in caps.

0
On

Go to solutions section if you are in a hurry.

The why

The reason for this error is the way bundling dynamic imports works in webpack. Imports go by providing a starting part of a path and you may refine that through an end part that holds a path that precise an extension.

Ex:

import(`codemirror/mode/${snippetMode}/${snippetMode}`)

ref: https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import

Such an import is processed by webpack through the following regex ^\.\/.*$. That we can see in the error message:

| <title>CodeMirror: RPM changes mode</title>
 @ ./node_modules/codemirror/mode/ sync ^\.\/.*$ ./rpm/changes/index.html

And that's basically to determine the possible modules. And bundle them as chunks for code splitting.

Because of that open matching regex. So all files of all types (extensions) are matched. Then depending on the extension. The correct loader would be used. If we had the html-loader setup and configured through a rule. That wouldn't fail. As the loader will handle that module (webpack module [html]). Even though that can be an option to handle the problem. It wouldn't be the right thing to do. As it would create an extra bundle for the module. And also some entries for it. In the main bundle.

Dynamic Imports types and there resolution

All types get code splitted (chunks created for them for code splitting). Except for the last one.

  • Absolute full path. (static) ==> Imported dynamically and code splitted.
    Ex:
import('codemirror/mode/xml/xml')

The exact path and module will be resolved and a chunk for it will be created.

  • Dynamic path with a starting path.
    Ex:
import(`codemirror/mode/${snippetMode}/${snippetMode}`)

All paths with this regex codemirror\/mode\/.*$ are matched.

  • Dynamic path with a starting path and an ending path with extension as well.
    ex:
import(`./locale/${language}.json`)

All paths with the following regex would be matched ./locale/.*\.json which limit it to .json extension module only.

  • Full variable import.
    Ex:
import(someVar)

Doesn't create code splitting at all. Neither tries to match. Because it can match just quite anything in the project. Even if the variable is set to a path someVar = 'some/static/path'. It wouldn't check and resolve the variable association.

The solutions

No electron or electron.

  • module.noParse
module: {
    noParse: /codemirror\/.*\.html$/,
    rules: [
    // ...

noParse tell webpack to not parse the matched files. So it both allows us to exclude files and boost the performance. It works all great. And can be one of the best solutions as well. You can use a regex. Or a function.

ref: https://webpack.js.org/configuration/module/#module-noparse

  • Don't use the import('some/path' + dynamicEl) format and use instead
const dynamicPath = 'some/path' + dynamicEl
import(dynamicPath) // That would work just ok
  • Use magic comments [wepackExclude or wepackInclude] with import (if electron and using require => convert require to import + magic comment). Magic comments don't work with require but only import.

ex:

import(
    /* webpackExclude: /\.html$/ */
    `codemirror/mode/${snippetMode}/${snippetMode}`
)

or even better

import(
    /* webpackInclude: /(\.js|\.jsx)$/ */
    `codemirror/mode/${snippetMode}/${snippetMode}`
)

Include allows us to make sure we are targeting exactly what we want to target. Excluding HTML. May still create chunks for some other files that are not .js. It's all relative to what we want. If you want to include only a specific extension. An include is better. Otherwise, if we don't know. And some other extension would be required. Then an exclude is better.

ref: https://webpack.js.org/api/module-methods/#magic-comments

Magic comments examples and usage:

// Single target
import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackExports: ["default", "named"] */
  'module'
);

// Multiple possible targets
import(
  /* webpackInclude: /\.json$/ */
  /* webpackExclude: /\.noimport\.json$/ */
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackPrefetch: true */
  /* webpackPreload: true */
  `./locale/${language}`
);

And to fully ignore code splitting

import(/* webpackIgnore: true */ 'ignored-module.js');
  • If nodejs or electron and the import should be processed as commonjs require. Use the externals config property like in the example bellow:
externals: {
    'codemirror': 'commonjs codemirror'
}

which works like this. Webpack for any import with codemirror (left part). convert it to the commonjs import without bundling it (right part).

import(`codemirror/mode/${snippetMode}/${snippetMode}`)

will be converted to:

require(`codemirror/mode/${snippetMode}/${snippetMode}`)

instead of bundled.

extenals doc ref: https://webpack.js.org/configuration/externals/#externals

  • If electron. And the feature should not be bundled. use window.require for all nodejs electron API calls (Otherwise set it on entry file window.require = require). And use it instead. Its electron, nodejs ==> Webpack stay the hell out of this