Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

ES6 Import & Export

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

If you watched episode 2 of our JavaScript series, then you know that ECMAScript is the official name of the JavaScript language standard and that ECMAScript version 6 - or ES6 - introduced the idea of modules. Modules are what we've been taking advantage of this entire tutorial: exporting and requiring values from different files.

But... surprise! In ECMAScript, the require() function does not exist. Whaaaat?! The require() statement was basically invented by Node, back before ES6: Node needed a way to require files, so they invented their own way. Later, ECMAScript decided to make the idea of modules part of the standard language. But when they did, they used a different keyword than require! Yep, there are two valid syntaxes for working with modules! But... it's not a big deal: they work exactly the same, except that the official syntax has one small advantage.

Hello import & export

Let's use the official module syntax. Instead of saying const Helper = require(), say import Helper from:

... lines 1 - 2
import Helper from './RepLogHelper';
... lines 4 - 215

It's that simple! And it doesn't change anything. In RepLogHelper, we also need to change our export to use the new syntax. Instead of module.exports = Helper, use export default Helper:

... lines 1 - 33
export default Helper;

We'll talk about what the default part means later. But for now, it's always export default and then what you want to export.

You can mix the two syntaxes - require and import - to a certain point, but you may run into some problems. Your best bet is to pick your favorite - mine is import and export - and use it everywhere. So let's update everything: import $ from 'jquery', import swal from 'sweetalert2' and import Routing from './Routing':

... lines 1 - 2
import Helper from './RepLogHelper';
import $ from 'jquery';
import swal from 'sweetalert2';
import Routing from './Routing';
... lines 7 - 215

At the bottom, use export default RepLogApp:

... lines 1 - 213
export default RepLogApp;

Cool! RepLogHelper is already ok, and in Routing.js, change this to: export default window.Routing:

... lines 1 - 4
export default window.Routing;

Keep going for the 3 entry files: import $ from 'jquery':

... lines 1 - 2
import $ from 'jquery';
... lines 4 - 12

If you don't need a return value, it's even easier: just import 'bootstrap'. Repeat that for the CSS files:

... lines 1 - 2
import $ from 'jquery';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.css';
import 'font-awesome/css/font-awesome.css';
import '../css/main.scss';
... lines 8 - 12

In login.js, import jQuery again, then import the CSS file:

... lines 1 - 2
import $ from 'jquery';
import '../css/login.css';
... lines 5 - 24

And one more time in rep_log.js: import jQuery and import RepLogApp:

... lines 1 - 2
import $ from 'jquery';
import RepLogApp from './Components/RepLogApp';
... lines 5 - 10

And... assuming I didn't mess anything up, our build should still be happy! Check out the terminal: yes! No errors. Move over to your browser and check it! Looks great!

Importing Named Modules

And... yea! That's it! Just two nearly-identical syntaxes... because... more is better?! The biggest reason I want you to know about import and export is so that you know what it means when you see it in code or documentation.

But, there is one small advantage to import and export, and it relates to this default keyword:

... lines 1 - 213
export default RepLogApp;

Usually, you'll want to export just one value from a module. In that case, you say export default and then you receive this value when using import.

But... technically... you can export multiple things from a module, as long as you give each of them a name. For example, instead of export default Helper, we could export an object with a Helper key and a foo key:

import {Helper, foo} from './RepLogHelper';

Then, the import has a slightly different syntax where you say explicitly which of those keys you want to import.

I don't usually do this in my code, but there is one case where it can be helpful. Imagine you're using a huge external library - like lodash - which is really just a collection of independent functions. If that library exports its values correctly, you could import just the functions you need, instead of importing the entire exported value:

import isEqual from 'lodash.isequal';

Then, at least in theory, thanks to a feature called "tree shaking", Webpack would realize that you're only using a few parts of that library, and only include those in the final, compiled file. In reality, this still seems a bit buggy: the unused code doesn't always get removed. But, the point is this: import and export have a subtle advantage and are the ECMAScript standard. So, use them!

Ok, it's time to find out how we can create a crazy-fast production build!

Leave a comment!

6
Login or Register to join the conversation
m3tal Avatar
m3tal Avatar m3tal | posted 2 years ago | edited

Hi

I have a file with multiple functions.
In the HTML


<li onclick="clickOnListElementGoFrom('parameter 1') class="list-group-item list-group-item-action">Option 1</li>

In the .js file I set the export for function as in .


export  { clickOnListElementGoFrom, getRelatedCitiesWeb }

in the app.js I have set the import.


import {clickOnListElementGoFrom, getRelatedCitiesWeb }  from "./flight";

and I always receiving a


Uncaught ReferenceError: clickOnListElementGoFrom is not defined
    at HTMLLIElement.onclick (eb5f9aaf04e3078e01a03dd62c568b27:278)
onclick @ eb5f9aaf04e3078e01a03dd62c568b27:278

Can somebody please help me with an idea?

Reply
Victor Avatar Victor | SFCASTS | m3tal | posted 2 years ago | edited

Hey Bogdan,

As far as I see it from the code you paste here - you missed a closing quote for that onclick attr, it should be like this I suppose:


<li onclick="clickOnListElementGoFrom('parameter 1')" class="list-group-item" list-group-item-action="">Option 1</li>

Please, first if all, double check your attribute quotes on that "li" tag and try again. Does it help?

If not, I probably may know what's the problem. Webpack isolates all that code in a special scope, so you can't access that function from outside. I'd recommend you to add a custom CSS class to that element, .e.g "js-custom-css-class" and then add a listener in that app.js where you import this function. If you need to pass an extra data from the HTML - you can use data attributes, e.g. data-parameter="1". Then, in the code you will be able to read that data. If you use jQuery - it will be as easy as call .data('parameter') on the jQuery element.

I hope this helps!

Cheers!

1 Reply
Chuck norris Avatar
Chuck norris Avatar Chuck norris | posted 5 years ago

Hi,

Again, great tutorials :).

I have one question regarding Encore. I'm using code splitting for importing js file only when needed.
It works fine, but the imported files are name only with numeric value (like 0.js, 1.js, etc ...).

I jnow we can change this when we use webpack directly, but I can't find any option in Encore.

Thanks for any help.

Reply

Hey Chuck norris!

Cool question :). The answer is that you do this the exact same way as in Webpack :), because the filename is determined at the time when you do the code splitting. The exact code depends on which method your using for splitting (require vs import). But, for example: https://webpack.js.org/guides/code-splitting/#dynamic-imports

Specifically, the do return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {

That should be all you need: there is nothing in your webpack config that needs to change for this :). If you see something different, let me know. But also... usually... those 0.js and 1.js names are fine - you shouldn't normally need to refer to these.

Cheers!

Reply
Chuck norris Avatar
Chuck norris Avatar Chuck norris | weaverryan | posted 5 years ago | edited

Hi Ryan,

thanks for your answer.
In my previous full front project, I used require.ensure, like this :

in webpack :


module.exports = {
  output : {
    chunkFilename: '[name].bundle.js',
  }

And in my app.js


if (something) {
  require.ensure([], () => {
    require('./my-component');
  }, 'my-component-name');
}

Now, in my symfony project, I'm using import like that :


if (something) {
  import(/* webpackChunkName: "my-component-name" */ './my-component').then(...).catch(...);
}

But this doesn't work.
I think its because there is no chunkFilename option inmy webpack.config.js
I don't knwo how to set it with Encore.
I looked inside Encore options and the closest I found is configureFilenames.

So I tried setting in webpack :


.configureFilenames({
   js: '[name].js',
 })

That doesn't work either.

Thanks for any help.

Reply

Hey Chuck norris!

Hmm, so I think you're getting confused because there are a few different ideas floating around at the same time. Let's see if we can clear then up!

1) You shouldn't need to do any special setup in Encore (or even Webpack) to get this lazy-chunk loading to work. That may not make sense yet, but keep reading :).

2) require.ensure and using import() as a function (like you did above) are actually different syntaxes for doing the exact same thing. For simplicity, let's use require.ensure, because using import() like this requires one extra bit of setup.

So, big picture: you normally run around using either require or import to get import a module you need. And, as you know, when you do this, Webpack/Encore will find all the pieces of JavaScript you need, and eventually put them into the final, built file - e.g. app.js.

But, suddenly, you realize that there is one module that you only need sometimes, and so you don't want it to be packaged in app.js: it makes that file unnecessarily big. So instead, you want to load this file only when you need it. That's when you use require.ensure or import() as a function. It should look similar to your code above:


// app.js
if (something) {
  require.ensure(['./my-component'], (require) => {
    // the last .default is needed if you're using the "export default" format in my-component
    const myComponent = require('./my-component').default;
    // use myComponent
  });
}

As soon as you have code like this (yea, the syntax is a bit odd), Webpack will NOT package my-component into the built app.js anymore. Instead, it will generate a separate JavaScript file that contains only the code for my-component. It will probably call this 0.js, but actually, you don't care :). Why? Because, at runtime, when your code gets inside the if statement, Webpack will automatically make an AJAX call to 0.js and then execute the callback (2nd argument to require.ensure) once that file has finished downloading. So, you never need to know or care what this filename is called: you just say "load this lazily", and Webpack takes care of the rest.

Does that makes sense? I was confused by how you seemed to try to lazily load this file in app.js and then also again in your "Symfony project" via a different method. Btw, when you mean "in your Symfony project", did you mean from inside a Twig template?

Let me know if this helps!

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.8.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.1.5
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.0.4
        "symfony/framework-bundle": "^4.0", // v4.0.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.7.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.1.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/validator": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.4
        "symfony/dotenv": "^4.0", // v4.0.4
        "symfony/phpunit-bridge": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0" // v4.0.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.19.0", // 0.19.0
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.4.1
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "node-sass": "^4.7.2", // 4.7.2
        "sass-loader": "^6.0.6", // 6.0.6
        "sweetalert2": "^7.11.0", // 7.11.0
        "webpack-notifier": "^1.5.1" // 1.5.1
    }
}
userVoice