Daniel Wiseman
Daniel Wiseman

Reputation: 31

Babel cannot parse files using Flow from a local custom module. Fails with the following error: `Module build failed...SyntaxError: Unexpected Token`

So I'm creating a web app, we'll call WebApp, which is a React module that uses components from a custom React module, we'll call CustomModule, that is also located on my local machine. I've been trying to import certain components from CustomModule in WebApp and have been running into the following error that appears when running webpack-dev-server -d in WebApp:

ERROR in /CustomModule/Components/LoadingSpinner/LoadingSpinner.jsx
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /CustomModule/Components/LoadingSpinner/LoadingSpinner.jsx: Unexpected token, expected ";" (11:17)

   9 |
  10 | export const Spinner = () => {
> 11 |     var imgStyle : object = {
     |                  ^
  12 |         height:"75%",
  13 |         padding:"2px 0 0 2px"
  14 |     } ;
    at Parser.raise (/WebApp/node_modules/@babel/parser/lib/index.js:3851:17)
    at Parser.unexpected (/WebApp/node_modules/@babel/parser/lib/index.js:5167:16)
    at Parser.semicolon (/WebApp/node_modules/@babel/parser/lib/index.js:5149:40)
    at Parser.parseVarStatement (/WebApp/node_modules/@babel/parser/lib/index.js:7763:10)
    at Parser.parseStatementContent (/WebApp/node_modules/@babel/parser/lib/index.js:7358:21)
    at Parser.parseStatement (/WebApp/node_modules/@babel/parser/lib/index.js:7291:17)
    at Parser.parseBlockOrModuleBlockBody (/WebApp/node_modules/@babel/parser/lib/index.js:7868:25)
    at Parser.parseBlockBody (/WebApp/node_modules/@babel/parser/lib/index.js:7855:10)
    at Parser.parseBlock (/WebApp/node_modules/@babel/parser/lib/index.js:7839:10)
    at Parser.parseFunctionBody (/WebApp/node_modules/@babel/parser/lib/index.js:6909:24)
 @ /CustomModule/Components/LoadingSpinner/index.js 1:0-65 1:0-65
 @ /CustomModule/Components/index.js
 @ ./ClientScripts/DataExplorer/Dashboard/containers/DashboardContent.jsx
 @ ./ClientScripts/DataExplorer/Dashboard/index.js
 @ ./ClientScripts/Route/RouteConfig.jsx
 @ ./ClientScripts/Route/index.js
 @ ./ClientScripts/Main.jsx
 @ ./ClientScripts/index.js
 @ multi (webpack)-dev-server/client?http://localhost:9000 ./ClientScripts/index.js

Both modules use Flow, babel, and webpack. I've set up the package.json, webpack.config.js, .flowconfig, and .babelrc files accordingly in each module. Then I symbolically linked CustomModule to WebApp using npm link. I build the CustomModule using webpack then attempt to build the WebApp which contains an import statement to use a component from CustomModule.

Versions

node: v9.0.0
npm: 5.5.1

@babel/cli: 7.2.3
@babel/core: 7.4.3
@babel/preset-flow: 7.0.0
babel-loader: 8.0.5
flow: 0.2.3
flow-webpack-plugin: 1.2.0
webpack: 4.16.5

WebApp webpack.config.js

const webpack = require('webpack');
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const FlowWebpackPlugin = require('flow-webpack-plugin');

module.exports = {
  entry: {
    vendor: ['babel-polyfill', 'react', 'react-dom'],
    annotationService: glob.sync('./ClientScripts/AnnotationService/*.js'),
    repositoryService: glob.sync('./ClientScripts/RepositoryService/*.js'),
    timelineService: glob.sync('./ClientScripts/TimelineService/*.js'),
    filterService: glob.sync('./ClientScripts/DataExplorer/Dashboard/FilterServices/*.js'),
    platform: './ClientScripts/index.js',
    objects: glob.sync("./ClientScripts/RepositoryService/Objects/*.js"),
    sass: './sass/main.scss'
  },
  output: {
    path: path.join(__dirname, 'reactDist'),
    filename: 'js/[name].js',
    sourceMapFilename: 'map/[name].map'
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },
  resolve: {
    alias: {
      Interfaces: path.resolve(__dirname, 'ClientScripts/Interfaces/'),
      Layout: path.resolve(__dirname, 'ClientScripts/Layout/'),
      Navigation: path.resolve(__dirname, 'ClientScripts/Navigation/'),
      Redux: path.resolve(__dirname, 'ClientScripts/Redux/'),
      RepositoryService: path.resolve(__dirname, 'ClientScripts/RepositoryService/'),
      TimelineService: path.resolve(__dirname, 'ClientScripts/TimelineService/'),
      FilterService: path.resolve(__dirname, 'ClientScripts/DataExplorer/Dashboard/FilterServices/'),
      AnnotationService: path.resolve(__dirname, 'ClientScripts/AnnotationService/'),
      Route: path.resolve(__dirname, 'ClientScripts/Route/'),
      Timeline: path.resolve(__dirname, 'ClientScripts/Timeline/'),
      TimelineEditor: path.resolve(__dirname, 'ClientScripts/TimelineEditor/'),
      Utilities: path.resolve(__dirname, 'ClientScripts/jsutils/'),
      ReactUtils: path.resolve(__dirname, 'ClientScripts/reactUtils/'),
      Images: path.resolve(__dirname, 'img/'),
    },
    symlinks: true
  },
  target: 'web',
  node: {
    fs: "empty"
  },
  externals: {
    'winston': 'require("winston")
  },
  module: {
    rules: [
      { test: /\.js$/, loader: 'babel-loader' },
      { test: /\.jsx$/, loader: 'babel-loader' },
      { test: /\.env$/, loader: "file-loader?name=index.[ext]", exclude: [/node_modules/] },
      {
        test: /\.scss$|\.css$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          use: [{
            loader: "css-loader",
            options: {
              minimize: true
            }
          },'sass-loader']
        })
      },
      { test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'file-loader?name=img/[name].[ext]?',
        options: {
          name (file) {
            if (process.env.environment === 'prod') {
              return '[path][name].[hash].[ext]'
            }

            return '[path][name].[ext]'
          }
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({ filename: 'css/timeline.[md5:contenthash:hex:20].css', disable: false, allChunks: true }),
    new FlowWebpackPlugin()
  ]
}

WebApp .flowconfig

[ignore]
.*/node_modules/flow-webpack-plugin/.*
.*/node_modules/.*\.json$
.*/node_modules/\.staging/.*

[libs]
flow-typed

[options]
module.name_mapper='^Interfaces\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Interfaces/\1'
module.name_mapper='^Layout\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Layout/\1'
module.name_mapper='^Navigation\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Navigation/\1'
module.name_mapper='^Redux\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Redux/\1'
module.name_mapper='^RepositoryService\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/RepositoryService/\1'
module.name_mapper='^Route\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Route/\1'
module.name_mapper='^Timeline\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/Timeline/\1'
module.name_mapper='^TimelineEditor\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/TimelineEditor/\1'
module.name_mapper='^Utilities\/\(.*\)$' -> '<PROJECT_ROOT>/ClientScripts/jsutils/\1'
module.name_mapper='^Images\/\(.*\)$' -> '<PROJECT_ROOT>/img/\1'
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.svg
module.file_ext=.json

CustomModule webpack.config.js

const webpack = require('webpack');
const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const FlowWebpackPlugin = require('flow-webpack-plugin');

module.exports = {
  entry: {
    vendor: ['babel-polyfill', 'react', 'react-dom'],
    components: './Components/index.js',
    sass: './sass/main.scss'
  },
  output: {
    path: path.join(__dirname, 'reactDist'),
    filename: 'js/[name].js',
    sourceMapFilename: 'map/[name].map'
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },
  resolve: {
    alias: {
      Components: path.resolve(__dirname, 'Components/'),
      Images: path.resolve(__dirname, 'img/'),
      Utilities: path.resolve(__dirname, 'Utilities/')
    },
    // extensions: ['', '.js', '.jsx']
  },
  target: 'web',
  node: {
    fs: "empty"
  },
  externals: {
    'winston': 'require("winston")'
  },
  module: {
    rules: [
      { test: /\.js$/, loader: 'babel-loader', exclude: [/node_modules/] },
      {
        test: /\.jsx$/,
        loader: 'babel-loader',
        exclude: [/node_modules/],
        query: {
          presets: ['@babel/preset-flow']
        }
      },
      { test: /\.env$/, loader: "file-loader?name=index.[ext]", exclude: [/node_modules/] },
      {
        test: /\.scss$|\.css$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          use: [{
            loader: "css-loader",
            options: {
              minimize: true
            }
          },'sass-loader']
        })
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'file-loader?name=img/[name].[ext]?',
        options: {
          name (file) {
            if (process.env.environment === 'prod') {
              return '[path][name].[hash].[ext]'
            }

            return '[path][name].[ext]'
          }
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({ filename: 'css/timeline.[md5:contenthash:hex:20].css', disable: false, allChunks: true }),
    new FlowWebpackPlugin(),
  ]
}

CustomModule .flowconfig

[ignore]
.*/node_modules/flow-webpack-plugin/.*
.*/node_modules/.*\.json$
.*/node_modules/\.staging/.*

[libs]
flow-typed

[options]
module.name_mapper='^Components\/\(.*\)$' -> '<PROJECT_ROOT>/Components/\1'
module.name_mapper='^Images\/\(.*\)$' -> '<PROJECT_ROOT>/img/\1'
module.name_mapper='^Utilities\/\(.*\)$' -> '<PROJECT_ROOT>/Utilities/\1'
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.svg
module.file_ext=.json

The same .babelrc is used for both modules

/*
    ./.babelrc
*/
{
    "presets":[
        "@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"
    ],
    "plugins": [
        "@babel/plugin-syntax-dynamic-import",
        "@babel/plugin-syntax-import-meta",
        "@babel/plugin-transform-flow-strip-types",
        "@babel/plugin-proposal-class-properties",
        "@babel/plugin-proposal-json-strings",
        [
            "@babel/plugin-proposal-decorators",
            {
                "legacy": true
            }
        ],
        "@babel/plugin-proposal-function-sent",
        "@babel/plugin-proposal-export-namespace-from",
        "@babel/plugin-proposal-numeric-separator",
        "@babel/plugin-proposal-throw-expressions"
    ]
}

I expect the result of the webpack-dev-server to yield no errors instead of the Module build failed error that is thrown above during babel parsing.

Upvotes: 0

Views: 785

Answers (1)

Nathan
Nathan

Reputation: 8151

Webpack's babel plugin is failing to parse your imgStyle object declaration within your custom LoadingSpinner.jsx file. This is because your style object, for your spinner, has a : slipped within your style object declaration.. This causes a parsing error since this isn't valid JavaScript syntax so you're getting the following syntax error:

SyntaxError: /CustomModule/Components/LoadingSpinner/LoadingSpinner.jsx: Unexpected token, expected ";" (11:17)

Looking at the stack trace, it points out that line 11 is the culprit:

  10 | export const Spinner = () => {
> 11 |     var imgStyle : object = {
     |                  ^
  12 |         height:"75%",
  13 |         padding:"2px 0 0 2px"
  14 |     } ;

Notice this line: var imgStyle : object = { // extra colon before the object declaration

Just remove the : within your custom spinner-style object declaration and webpack's babel plugin should now be able to successfully parse this file. Something like the following:

var imgStyleObject = {
   height:"75%",
   padding:"2px 0 0 2px"
};

Hopefully that helps!

Upvotes: 1

Related Questions