symfony / webpack-encore

A simple but powerful API for processing & compiling assets built around Webpack
https://symfony.com/doc/current/frontend.html
MIT License
2.23k stars 199 forks source link

Material.io components are not transpiled to ES5 #293

Open vkost opened 6 years ago

vkost commented 6 years ago

Hi, I'm trying to add material.io components to a Symfony project, using Encore. Apparently I'm doing something wrong since the resulting JS contains raw ES6 from Material components. However, when I use webpack directly, not configured via Encore, everything transpiles normally. Same files, same node_modules, just plain webpack config.

Here's my Encore webpack config that does not work

Encore
    .setOutputPath('web/build/')
    .setPublicPath('/build')
    .addEntry('app', './assets/js/app.js')
    .addStyleEntry('bundle', './assets/css/app.scss')
    .enableSassLoader(function(options) {
        options.includePaths = ['node_modules']
    })
    .autoProvidejQuery()
    .enableSourceMaps(!Encore.isProduction())
    .cleanupOutputBeforeBuild()
    .configureBabel(config => {
        config.presets = ['es2015'];
    });

And this is plain webpack config that DOES work

const path = require('path');
const glob = require('glob');

module.exports = [];

module.exports.push({
    entry: './assets/css/app.scss',
    output: {
        // This is necessary for webpack to compile
        // But we never use style-bundle.js
        filename: 'style-bundle.js',
    },
    module: {
        rules: [{
            test: /\.scss$/,
            use: [
                {
                    loader: 'file-loader',
                    options: {
                        name: './web/build/bundle.css',
                    },
                },
                { loader: 'extract-loader' },
                { loader: 'css-loader' },
                {
                    loader: 'sass-loader',
                    options: {
                        includePaths: ['node_modules', 'node_modules/@material/!*']
                            .map((d) => path.join(__dirname, d))
                            .map((g) => glob.sync(g))
                            .reduce((a, c) => a.concat(c), [])
                    }
                }
            ]
        }]
    },
});

module.exports.push({
    entry: "./assets/js/app.js",
    output: {
        filename: "./web/build/build.js"
    },
    module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel-loader',
            options: {
                presets: ['env']
            }
        }]
    },
});

Don't get confused by different output files - I did this so I can have both to compare. But everything that is produced by Encore contains raw ES6 while same sources processed with standard webpack config produces ES5. The babel config I added (add es2015 preset) was an attempt to force transpiling, however normal webpack config works fine with preset: env...

Here's my package.json:

{
  "devDependencies": {
    "@symfony/webpack-encore": "^0.19.0",
    "webpack-notifier": "^1.6.0",
    "babel-preset-es2015": "^6.24.1",
    "extract-loader": "^2.0.1",
    "material-components-web": "^0.33.0",
    "node-sass": "^4.8.3",
    "sass-loader": "^6.0.7"
  },
  "dependencies": {
    "fullcalendar": "^3.9.0",
    "moment-timezone": "^0.5.14",
    "symfony-collection": "^2.1.25"
  }
}

Ignore dependencies - not used in source code at all (at the moment)

The source is also very simple (for testing), it merely imports one Material component and intialises it, like this

// app.js
import {MDCTemporaryDrawer} from '@material/drawer';
let drawer = new MDCTemporaryDrawer(document.querySelector('#main-drawer'));
drawer.open = true;

This actually works in FF, because it understands ES6, but I see raw ES6 in resulting packed file, and because of that I start getting errors when I try to add more complex stuff.

Any help will be appreciated - I'd like to stick to Encore because Symfony is sticking with it, but I'm out of ideas :)

Thanks Vic

Lyrkan commented 6 years ago

Hey @vkost,

I did a quick test and got the following error from UglifyJs: Unexpected token: name (MDCPersistentDrawerFoundation), so that issue is probably related to #139 (you'll find some workarounds there).

To sum up why it fails:

vkost commented 6 years ago

Hi @Lyrkan, thank you for testing this... I understand your explanation but I'd like to learn more on this, if possible:

  1. Why does encore exclude node_modules from babel processing?
  2. Can I switch this off, if needed, since I don't believe Material.io will start to ship with transpiled versions any time soon...

Thanks

Lyrkan commented 6 years ago

@vkost For the first question I'd say that a lot of libs already provide a compiled version and that making them going through Babel would just be a waste of compilation time. As I said earlier you can find some workarounds in the other thread (for instance, if you wish to remove the exclude: https://github.com/symfony/webpack-encore/issues/139#issuecomment-322592989).

vkost commented 6 years ago

OK @Lyrkan I understand why you excluded node_modules, it does make sense to use the pre-compiled version. I have just checked, and there is a precompiled version of @material modules in /dist, relative to module path. Can you point out any lib with precompiled modules, I'd like to learn more on the issue?

Thanks

Lyrkan commented 6 years ago

@vkost as pointed out in #139 Bootstrap also has that issue if, for instance, you require bootstrap/js/src/modal instead of bootstrap/js/dist/modal. An in that case their doc recommend to use the "dist" version: https://getbootstrap.com/docs/4.1/getting-started/webpack/#importing-javascript

Note that this will not be needed with Webpack 4 since it includes a version of Uglify-JS that supports ES6+.

Guikingone commented 6 years ago

I don't know if the issue is solved but here's my configuration (which works with Material Components):

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

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    // uncomment to create hashed filenames (e.g. app.abc123.css)
    // .enableVersioning(Encore.isProduction())
    .enableTypeScriptLoader()
    .enableVueLoader()
    .enableSassLoader()
    .addLoader(
        {
            test: /\.scss$/,
            use: [
                {
                    loader: 'sass-loader',
                    options: {
                        importer: function(url, prev) {
                            if(url.indexOf('@material') === 0) {
                                const filePath = url.split('@material')[1];
                                const nodeModulePath = `./node_modules/@material/${filePath}`;
                                return {
                                    file: require('path').resolve(nodeModulePath)
                                };
                            }
                            return {
                                file: url
                            };
                        }
                    }
                }
            ]
        }
    )
    .addLoader(
        {
            test: /\.js$/,
            loader: 'babel-loader',
            query: {
                presets: ['es2015']
            }
        }
    )

    // Style
    .addStyleEntry('core', './assets/scss/public/core.scss')
    .addStyleEntry('registration', './assets/scss/public/registration.scss')

    // Javascript
    .addEntry('form', './assets/javascript/components/form.js')
    .addEntry('snackbar', './assets/javascript/components/form/snackbar.js')

    // PWA
    .addEntry('serviceWorker', './assets/javascript/pwa/app.js')
    .addEntry('sw', './assets/javascript/pwa/sw.js')

    // Vue
    .addEntry('vue', './assets/vue/public/index.js')
;

module.exports = Encore.getWebpackConfig();

Here's the package.json one:

{
    "devDependencies": {
        "@material/base": "^0.34.0",
        "@symfony/webpack-encore": "^0.20.0",
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.4",
        "babel-preset-es2015": "^6.24.1",
        "css-loader": "^0.28.11",
        "extract-loader": "^2.0.1",
        "file-loader": "^1.1.11",
        "node-sass": "^4.8.3",
        "sass-loader": "^7.0.1",
        "ts-loader": "3.5.0",
        "typescript": "^2.7.2",
        "vue": "^2.5.16",
        "vue-loader": "^14.2.2",
        "vue-template-compiler": "^2.5.16"
    },
    "license": "UNLICENSED",
    "private": true,
    "scripts": {
        "dev-server": "encore dev-server",
        "dev": "encore dev",
        "watch": "encore dev --watch",
        "build": "encore production"
    },
    "dependencies": {
        "@material/auto-init": "^0.32.0",
        "@material/button": "^0.33.0",
        "@material/snackbar": "^0.34.0",
        "material-components-web": "^0.33.0"
    }
}

As far as I can see, the loader for JS should be loaded via a new Loader, even if Encore use the default one, I don't really know why but Encore seems to not call the right loader at the compilation time.