Webpack-encore: How to use React with ES6

Created on 31 Oct 2017  路  10Comments  路  Source: symfony/webpack-encore

I'd like to use ES6 to write React components.

Currently I've configured Encore as the documentation suggests:

var Encore = require('@symfony/webpack-encore');

Encore
    // directory where all compiled assets will be stored
    .setOutputPath('web/build/')

    // what's the public path to this directory (relative to your project's document root dir)
    .setPublicPath('/build')

    // empty the outputPath dir before each build
    .cleanupOutputBeforeBuild()

    // will output as web/build/app.js
    .addEntry('main', './src/AppBundle/Resources/assets/js/_main.js')

    // will output as web/build/global.css
    .addStyleEntry('global', './src/AppBundle/Resources/assets/css/_global.scss')

    // allow sass/scss files to be processed
    .enableSassLoader()

    // allow legacy applications to use $/jQuery as a global variable
    .autoProvidejQuery()

    .enableSourceMaps(!Encore.isProduction())

    // create hashed filenames (e.g. app.abc123.css)
    .enableVersioning()

    // Enable react
    .enableReactPreset()
;

// export the final configuration
module.exports = Encore.getWebpackConfig();

So I'm using enableReactPreset().

But how can I use also ES6 transpiling?

This is my very first attempt to use it, and I'm a real beginner in the Javascript world, so, please, be very patient :)

question

Most helpful comment

If it helps, this is what I am using and it supports arrow functions, spread operators etc.

`
    // webpack.config.js
    var Encore = require('@symfony/webpack-encore');

    Encore
    // directory where all compiled assets will be stored
    .setOutputPath('web/build/')

    // what's the public path to this directory (relative to your project's document root dir)
    .setPublicPath('/build')

    // empty the outputPath dir before each build
    .cleanupOutputBeforeBuild()

    // will output as web/build/app.js
    .addEntry('app', ["babel-polyfill", './assets/js/index.js'])

    // will output as web/build/global.css
    .addStyleEntry('global', './assets/css/global.scss')

    // allow sass/scss files to be processed
    .enableSassLoader()

    // allow legacy applications to use $/jQuery as a global variable
    .autoProvidejQuery()

    .enableSourceMaps(!Encore.isProduction())

    // Enable ReactJS
    .enableReactPreset()

    .configureBabel(function(babelConfig) {
        babelConfig.plugins = ["transform-object-rest-spread","transform-class-properties"]
    })

// create hashed filenames (e.g. app.abc123.css)
// .enableVersioning()
;

// export the final configuration
module.exports = Encore.getWebpackConfig();`

As you can guess, I had to install those two babel plugins but it all works fine now. Please feel free to ask for more details if you need any.

Thanks,
Abhinav

All 10 comments

Hi @Aerendir,

Unless I'm misunderstanding your question I'd say that you don't have anything else to do.

You should be able to write your code using ES6 and it should be transpiled correctly by Babel using the default settings provided by Encore (unless you have a .babelrc file in your project), in this case:

{
    cacheDirectory: true,
    presets: [
        ['env', {
            modules: false,
            targets: {
                browsers: '> 1%',
                uglify: true
            },
            useBuiltIns: true
        }],
        'react'
    ],
    plugins: []
}

@Lyrkan it seems it is not sufficient.

If I try to compile this code:

export default class Todo extends React.Component {
    constructor(props) {
        super(props);

        // Set initial state
        this.state = {
            data: props.data
        }
    }

    changeStatus = () => {
        let update = {
            opened: !this.state.data.opened
        };

        // Retrieve the todos of the list
        Axios.put(this.state.data['@id'], update)
            .then((response) => {
                let data = this.state.data;
                data.opened = !data.opened;
                this.setState({data: data});
            })
            .catch(function (error) {
                console.log(error);
            });
    }

    render() {
        let status = this.state.data.opened ? 'opened' : 'closed';
        return (
            <li className={"todo " + status} data-endpoint={this.state.data['@id']}>
                <div className="status">
                    <input type="checkbox" checked={!this.state.data.opened} onChange={this.changeStatus}/>
                </div>
                <a href={window.Routing.generate('todo_list_todo_show', {account: this.props.todoList.account.id, list: this.props.todoList.id, todo: this.state.data.id}, true)}>{this.state.data.name}</a>
            </li>
        );
    }
}

This is the error I receive:

Aerendir$ ./node_modules/.bin/encore/ dev
Running webpack ...

(node:1737) DeprecationWarning: Chunk.modules is deprecated. Use Chunk.getNumberOfModules/mapModules/forEachModule/containsModule instead.
 ERROR  Failed to compile with 1 errors                                                                                                                                                                                  5:06:32 PM

 error  in ./src/Coommercio/Bundle/TodoBundle/Resources/assets/js/Todo.jsx

Syntax Error: Unexpected token (20:17)

  18 |     }
  19 | 
> 20 |     changeStatus = () => {
     |                  ^
  21 |         let update = {
  22 |             opened: !this.state.data.opened
  23 |         };



 @ ./src/Coommercio/Bundle/TodoBundle/Resources/assets/js/TodoList.jsx 18:0-26
 @ ./src/Coommercio/Bundle/TodoBundle/Resources/assets/js/TodoBundle.jsx
 @ ./src/AppBundle/Resources/assets/js/_main.js

So there is something I'm missing, but I don't know what...

An ES6 class can only contain method definitions, not assignments.

In your case you should probably have something like this:

export default class Todo extends React.Component {
    constructor(props) {
        // ...
    }

    changeStatus() {
        // ...
    }

    render() {
        // ...
    }
}

Having assignment of arrow functions in a class is not part of ES6. It is part of a draft proposal for an upcoming ES version.
Babel is able to transpile it, but you will need to enable this transformation in the babelrc file. The default babel config shipped in Encore when you don't configure babel does not enable the transpilation of experimental features.

It is something suggested here: https://stackoverflow.com/a/47031631/1399706

But eventually the user who replied made an error calling it an ES6 feature...

Reading further, it seems that to use the feature I have to install stage-1 Babel preset.

At this point, the question is: how can I do this with Encore?

Add it first using yarn: yarn add --dev babel-preset-stage-1
Then call the configureBabel() method in your webpack.config.js file:

.configureBabel((config) => {
    config.presets.push('stage-1');
})

It looks like it is now in the stage-3 preset by the way, so you could also probably use that one.

Another option would be to directly use the transform-class-properties plugin if you only need that feature.

@Aerendir Just to keep things in perspective, the feature you're trying to use is still a draft feature. That's why using it (and setting up Webpack/Encore to compile it) take a bit more setup. You can totally use it - but since you mentioned you're still new to the JavaScript world, it might be even better to just avoid the feature.

If I understand things correctly, you could make your code look like the earlier suggestion - https://github.com/symfony/webpack-encore/issues/193#issuecomment-340817386 - and change this.changeStatus to this.changeStatus() in render (i.e. call it like a function).

Let us know what you find out! :)

If it helps, this is what I am using and it supports arrow functions, spread operators etc.

`
    // webpack.config.js
    var Encore = require('@symfony/webpack-encore');

    Encore
    // directory where all compiled assets will be stored
    .setOutputPath('web/build/')

    // what's the public path to this directory (relative to your project's document root dir)
    .setPublicPath('/build')

    // empty the outputPath dir before each build
    .cleanupOutputBeforeBuild()

    // will output as web/build/app.js
    .addEntry('app', ["babel-polyfill", './assets/js/index.js'])

    // will output as web/build/global.css
    .addStyleEntry('global', './assets/css/global.scss')

    // allow sass/scss files to be processed
    .enableSassLoader()

    // allow legacy applications to use $/jQuery as a global variable
    .autoProvidejQuery()

    .enableSourceMaps(!Encore.isProduction())

    // Enable ReactJS
    .enableReactPreset()

    .configureBabel(function(babelConfig) {
        babelConfig.plugins = ["transform-object-rest-spread","transform-class-properties"]
    })

// create hashed filenames (e.g. app.abc123.css)
// .enableVersioning()
;

// export the final configuration
module.exports = Encore.getWebpackConfig();`

As you can guess, I had to install those two babel plugins but it all works fine now. Please feel free to ask for more details if you need any.

Thanks,
Abhinav

Thank you for all of your replies!

For the moment I decided to go the standard way, to not keep too many things on the table: I prefere to first learn the standard way of doing things, then draft features when I really need it.

In the mean time, I'll keep this post in the references so, when I will be ready, I'll have all the information at hand!

Thank you again for your support!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wenmingtang picture wenmingtang  路  4Comments

Growiel picture Growiel  路  4Comments

JohnnyEvo picture JohnnyEvo  路  3Comments

iammichiel picture iammichiel  路  3Comments

heitorvrb picture heitorvrb  路  4Comments