What's the behavior of webpack imports in ReactJS project? - javascript

I've used multiple 3rd party libraries in my ReactJS project like lodash, d3 etc. I just found out that I've not written explicit imports
like
import d3 from 'd3'
or import _ from 'lodash'
in all my components (I've imported them in some though). Yet it all works fine and I can also get the d3 object and _ in the browser console. How is it supposed to be?
Considering this is okay behavior can I just import the node_modules dependencies for react only once in my App(Root) Component and don't import them at all in all the other child components.
P.S I'm using webpack 1 and I've verified the behavior.

Even when it works, it is a bad practice, so my advice would be to play nice and always explicitly import modules you are using.
The reason why it is working is probably because some of those modules declare globals when they are imported, so your components that do not import them still reach global.

Related

Implications of doing `import * as React from 'react'` vs `import React, { useEffect } from 'react'` [duplicate]

I've noticed that React can be imported like this:
import * as React from 'react';
...or like this:
import React from 'react';
The first imports everything in the react module (see: Import an entire module's contents)
The second imports only the default module export (see: Importing defaults)
It seems like the two approaches are different and fundamentally incompatible.
Why do they both work?
Please reference the source code and explain the mechanism...I'm interested in understanding how this works.
Update
This is not a duplicate of What is the difference between import * as react from 'react' vs import react from 'react'
That question was answered with general ES6 module information.
I am asking about the mechanism that makes the react module work like this. It seems to be related to "hacky" export mechanism in the source here but it's not clear how that enables both importing the entire module and just the default export into React and having both of those approaches work with transpiling JSX, etc.
TL;DR
Indeed ES import statements import default and import * are not the same thing, the fact that they behave the same in this case is a combination of how React authors chose to publish the library and compatibility layers in TypeScript (using esModuleInterop) or Babel and your bundler to make them "just work". It probably shouldn't work according to ES6 spec, but today we are still working in an era where JS modules are a mess, so tools like Babel, TypeScript, Webpack, etc try to normalize behavior.
More details:
React is not an ES6 library. If you look at the source code you see this in index.js:
const React = require('./src/React');
// TODO: decide on the top-level export form.
// This is hacky but makes it work with both Rollup and Jest.
module.exports = React.default || React;
(Note the comment, even in React source code they struggle with ES6 default export compatibility.)
The module.exports = syntax is CommonJS (NodeJS). A browser would not understand this. This is why we use bundlers like Webpack, Rollup, or Parcel. They understand all kinds of module syntax and produce bundles that should work in the browser.
But even though React is not an ES library, both TypeScript and Babel let you import it as if it is (using import syntax, rather than require(), etc), but there are differences between CJS and ES that have to be resolved. One of them is the fact that export = can give you things that ES has no spec-compliant way to import, like a function or a class as the module. To work around these incompatibilities Babel has for awhile allowed you to import CJS modules as if they were exporting something by default, or import as a namespace. TypeScript for awhile didn't do this, but more recently added that as an option under esModuleInterop. So now both Babel and TypeScript can pretty consistently allow a CJS module to be imported using default or namespace ES imports.
With TypeScript it also depends on how the type-definitions for the library are actually defined. I won't get into that, but you can imagine situations where thanks to transpilers and bundlers a particular import works at runtime, but TypeScript doesn't compile without errors.
Another thing worth mentioning is that if you look at the built code for React there is a UMD module version as well as the CJS version. The UMD version includes some gnarly runtime code to try to make it work in any module environment, including the browser. It's mainly for use if you want to just include React at runtime (ie you don't use a bundler). Example.
Confusing? Yeah, I think so. :)
You most likely have "allowSyntheticDefaultImports": true, set in your tsconfig.json, which essentially shuts the compiler up about default imports it thinks are invalid. Typescript added esModuleInterop which does essentially what babel does for module loading.
This allows you to use ES6 default imports even when the source code you're importing doesn't export anything as default
Typescript is strict (follows the rules) when it comes to this, which is why they require you to import * as React from 'react'. Or requires you to tell it to allow synthetic default imports in its base config.
More On That Here

How can I exclude an ES6 module (VueJS) from treeshaking in RollupJS? (and should I?)

Right now I pull in all my own es6 modules and create a bundle using Rollup.
Recently I started using VueJS, which now has an ES6 Module which can be pulled in just like my own modules. Rollup does some treeshaking on it, but I don't know if that is a very good idea? I don't know what it is doing, so I would rather it does nothing!
Instead I just add vue at the end of my HTML:
<script src="dist/bundle.js"></script>
I love the convenience of having everything as one bundled file, but should I really treeshake the entire Vue app, is there a command in Rollup that I can not treeshake just this one module?
EDIT
I have found the --external option, which seems good as it would just keep the import for vue and bundle the rest, but it does not seem to work!
When I use rollup --format=iife --external=../node_modules/vue/dist/vue.esm.browser.js --file=dist/bundle.js -- src/main.js it says Error: Could not resolve '../node_modules/vue/dist/vue.esm.browser.js' from src/app.js.
In my main.js it has import Vue from '../node_modules/vue/dist/vue.esm.browser.js; which works fine for the app. I want to make Vue an external, but it won't work!
To prevent Rollup from treeshaking a particular module, you can simply import it blindly (instead of a part of it), so that Rollup thinks the module performs some side effect:
import 'vue'
Of course you can still import some bits in parallel, so that you can rename the default export for example:
import 'vue'
import Vue from 'vue'
As for your --external option, you probably just need to wrap the path value with quotes:
--external='../node_modules/vue/dist/vue.esm.browser.js'
Note that you should probably switch to Rollup configuration file (instead of CLI options) to make your life easier. You will also be able to use rollup plugins, e.g. rollup-plugin-alias to manage the exact location of the Vue file you want to use.

Does ReactJS reuse imported packages?

Does ReactJS reuse imported packages?
Let's say I have a file called DisplayItems.js and EditItem.js. They are both imported into App.js.
If I import a package (like axios) at the top of my DisplayItem.js file (import axios from 'axios';), and I also import it in my EditItem.js file, does my Application grow by 13kb or 26kb (assuming axios is 13kb)?
This behavior is controlled not by React, but by whatever build tool compiles and bundles your import statements into a JavaScript file for the browser.
The Create React App template currently uses Webpack as its build tool. Webpack avoids duplicating code that is imported multiple times; it only writes the definitions once. If you are using a different project setup for your React app, your project may use a different build tool.
In response to jhpratt, I did think of testing it on my own, but I knew it would take some time (about 34 mins).
Here's the test results.
Importing jQuery
2742120 (the control) - with jquery imported once
2742353 - with jquery imported twice (233 byte difference, .2kb)
2741887 - with jquery not being imported (233 byte difference, .2kb)
Importing modal-vanilla
2742120 (the control) - with modal-vanilla being imported once
2742406 - with modal-vanilla being import twice (286 byte difference, .3kb)
2712501 - with modal-vanilla not being imported (29386 byte difference, 29.3kb)
I'm not sure what was going on with jQuery (maybe I added it somewhere else in my project?), but it does look like packages are reused (at least in this instance).
Just in case anyone's wondering, I am using Laravel's React setup.

How to import mutiple modules from webpack?

I've around 20 jsx files with the few repeated imports like :
import React from 'react';
import { Form } from 'formsy-react';
import AppActions from '../../utils/actions/app-actions';
import store, {formStore} from '../../utils/stores/stores';
import AppConstants from '../../utils/constants/app-constants';
which cause my bundle.js weight more than 1.7 Mb even after minification.
Is there any possiblity to import these modules from any where else so that I don't need to import these again and again.
I think there is a misunderstanding here between Webpack, ES6 and a module.
Webpack is going to analyse your javascript code and detect the dependencies, based on that it will include the modules you need in your bundle, there is no duplicate there, every module is added only once in the proper order to resolve the dependencies while avoiding duplicates of code.
ES6 import export syntax convention requires to define the imports you need in every file that requires them, so browsers or tools like Webpack can properly detect dependencies and only use files they need.
1.7 Mb can be considered as a big file for a web page, but with a proper caching/minification it can be loaded instantly

Top level javascript imports - Redux

I'm trying to learn redux and I've run into an error. I only have two files, an index.html file and a main.js file, there are links to the jquery and redux cdns in the html file. I've only gotten to 2.3 in the redux tutorial(http://redux.js.org/docs/basics/Store.html) and am stuck. I have
import {createStore} from 'redux';
at the top of my main.js file, but when I load the application, I get an error pointing to line above saying
SyntaxError: import declarations may only appear at top level
What is a 'top level import declaration'?
Here is a gist to my code if that helps. https://gist.github.com/austincarvey/6e6c8fdc2640b0f9bbfb
The import directive is not recognised by web browsers. It's used at the compilation stage, to tie together different source files and third party modules into a single web bundle. If that doesn't make sense, then I highly recommended learning Babel and either Webpack or Browserify. Babel transpiles ES6 and React syntax to browser friendly ES5 code, whilst Webpack/Browserify bundle your modules.
For now however, if you just want to get on with learning Redux, you can simply remove the import statement and instead use the global variable Redux exposed by the redux CDN script in your gist. i.e.
var store = Redux.createStore(counterReducer);
import is used when you are including a file via es6 modules in a context that supports them (Browserify/Webpack/etc, future versions of browsers).
Since you are including the Redux lib via <script> tag, that takes care of including Redux in the global scope.
In the case of your gist, if you erase line one and change the createStore invocation to Redux.createStore on 29, everything should work.

Categories