It is my understanding that the following two lines are equivalent:
const Up = require('write-up').default
And...
import Up from 'write-up'
Both examples should make the default export of the write-up module available as Up.
Unfortunately, using Babel and Webpack, that's not the behavior I'm seeing.
The first example works just fine. It produces this line:
var Up = __webpack_require__(5).default;
Up is set to the default export of the write-up module, which is the behavior I expect.
The second example does not work. It produces this:
var _writeUp = __webpack_require__(5);
var _writeUp2 = _interopRequireDefault(_writeUp);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
Instead of setting Up to the default export, Up is set to an object containing every single export of the write-up module (which includes the default field).
What am I doing wrong?
Here's the relevant portion from my Webpack config:
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
Babel-compiled import statements have a dual behavior. If the module being imported was compiled from ES6 export statements with Babel, then
import foo from 'foo';
will behave (mostly) like
const foo = require('foo').default;
but if foo was not compiled with Babel, or something that tries to be compatible with Babel, then as far as Babel is concerned, it has no special behavior and is a normal CommonJS module. In that case, which appears to be your case, it will behave like
const foo = require('foo');
Babel does this so that you can import normal CommonJS modules like
import fs from 'fs';
where fs is a standard Node module and has no .default property.
Related
I need to do something like:
if (condition) {
import something from 'something';
}
// ...
if (something) {
something.doStuff();
}
The above code does not compile; it throws SyntaxError: ... 'import' and 'export' may only appear at the top level.
I tried using System.import as shown here, but I don't know where System comes from. Is it an ES6 proposal that didn't end up being accepted? The link to "programmatic API" from that article dumps me to a deprecated docs page.
We do have dynamic imports proposal now with ECMA. This is in stage 3. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (condition) {
import('something')
.then((something) => {
console.log(something.something);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also have other features like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
If you'd like, you could use require. This is a way to have a conditional require statement.
let something = null;
let other = null;
if (condition) {
something = require('something');
other = require('something').other;
}
if (something && other) {
something.doStuff();
other.doOtherStuff();
}
You can't import conditionally, but you can do the opposite: export something conditionally. It depends on your use case, so this work around might not be for you.
You can do:
api.js
import mockAPI from './mockAPI'
import realAPI from './realAPI'
const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI
apiConsumer.js
import API from './api'
...
I use that to mock analytics libs like mixpanel, etc... because I can't have multiple builds or our frontend currently. Not the most elegant, but works. I just have a few 'if' here and there depending on the environment because in the case of mixpanel, it needs initialization.
2020 Update
You can now call the import keyword as a function (i.e. import()) to load a module at runtime. It returns a Promise that resolves to an object with the module exports.
Example:
const mymodule = await import('modulename');
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
or:
import('modulename')
.then(mymodule => {
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
});
See Dynamic Imports on MDN
Looks like the answer is that, as of now, you can't.
http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api
I think the intent is to enable static analysis as much as possible, and conditionally imported modules break that. Also worth mentioning -- I'm using Babel, and I'm guessing that System is not supported by Babel because the module loader API didn't become an ES6 standard.
Import and Export Conditionally in JS
const value = (
await import(`${condtion ? `./file1.js` : `./file2.js`}`)
).default
export default value
Important difference if you use dynamic import Webpack mode eager:
if (normalCondition) {
// this will be included to bundle, whether you use it or not
import(...);
}
if (process.env.SOMETHING === 'true') {
// this will not be included to bundle, if SOMETHING is not 'true'
import(...);
}
require() is a way to import some module on the run time and it equally qualifies for static analysis like import if used with string literal paths. This is required by bundler to pick dependencies for the bundle.
const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;
For dynamic module resolution with complete static analysis support, first index modules in an indexer(index.js) and import indexer in host module.
// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';
// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
obscuring it in an eval worked for me, hiding it from the static analyzer ...
if (typeof __CLI__ !== 'undefined') {
eval("require('fs');")
}
Conditional imports could also be achieved with a ternary and require()s:
const logger = DEBUG ? require('dev-logger') : require('logger');
This example was taken from the ES Lint global-require docs: https://eslint.org/docs/rules/global-require
I was able to achieve this using an immediately-invoked function and require statement.
const something = (() => (
condition ? require('something') : null
))();
if(something) {
something.doStuff();
}
Look at this example for clear understanding of how dynamic import works.
Dynamic Module Imports Example
To have Basic Understanding of importing and exporting Modules.
JavaScript modules Github
Javascript Modules MDN
No, you can't!
However, having bumped into that issue should make you rethink on how you organize your code.
Before ES6 modules, we had CommonJS modules which used the require() syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code. - source: https://bitsofco.de/what-is-tree-shaking/
I guess one of the reasons they dropped that support on ES6 onward is the fact that compiling it would be very difficult or impossible.
One can go through the below link to learn more about dynamic imports
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports
I know this is not what the question is asking for, but here is my approach to use mocks when using vite. I'm sure we can do the same with webpack and others.
Suppose we have two libraries with same interface: link.js and link-mock.js, then:
In my vite.config.js
export default defineConfig(({ command, mode }) => {
const cfg = {/* ... */}
if (process.env.VITE_MOCK == 1) {
cfg.resolve.alias["./link"] = "./link-mock"; // magic is here!
}
return cfg;
}
code:
import { link } from "./link";
in console we call:
# to use the real link.js
npm run vite
# to use the mock link-mock.js
VITE_MOCK=1 npm run vite
or
package.json script
{
....
"scripts": {
"dev": "vite",
"dev-mock": "VITE_MOCK=1 vite"
}
}
I am currently using webpack to bundle up a javascript file with react. This file also has another import statement to another module within my project
import { MyModule} from './MyModuleFile.js'
Is there a way to exclude MyModule.js from the webpack bundle, and instead keep the import as is?
What you're after might be the externals option in your webpack.config.js
module.exports = {
//...
externals: {
'./MyModuleFile': 'MyModule',
}
};
This assumes you will include the script in your HTML manually which will expose the MyModule global
If you'd instead really like to use ES6 import, you could use the same technique because everything you put in there will just be exported as is
module.exports = {
//...
externals: {
'./MyModuleFile': 'import("MyModuleUrl")',
}
};
But make sure you use the async version of import
import('./MyModuleFile').then(({default: MyModule}) => doSomethingWith(MyModule));
Or alternatively use the webpackIgnore comment to keep the import as is without changing the config
import(/* webpackIgnore: true */'MyModuleUrl').then(({default: MyModule}) => doSomethingWith(MyModule));
Just add it to the exclude option in your loader configuration of your webpack.config.js:
rules: [
// rules for modules (configure loaders, parser options, etc.)
{
test: /\.js$/,
exclude: [
/node_modules/,
/MyModuleFile/
],
...
}
]
https://webpack.js.org/configuration/module/#rule-exclude
I have a CommonJS module, called inner.js, that defines a function and then exports that function as the entire module:
// inner.js, a legacy CommonJS module
var foo = function() { return 42; };
module.exports = foo;
In Node, I can readily verify this works as-is.
> var inner = require('./inner.js');
> inner() // prints 42
But that’s a legacy module that I’d like to use from a ES6 module, called outer.js:
// outer.js, an ES6 module
import * as inner from "./inner.js";
export function bar() { return inner(); }
I see that rollup-plugin-commonjs is commonly used in these situations but I can’t get it to work when the CommonJS inner.js module exports a function as the whole module. If, after running rollup and dumping the result to loadme.js, I try to run load the ES6 outer module and try to call the function originally defined in the inner CommonJS module, I get an error:
> var outer = require('./loadme.js')
undefined
> outer.bar()
TypeError: inner$2 is not a function
at Object.bar (/.../so-rollup-question/loadme.js:27:25)
I think I’m just failing to load the CommonJS module correctly, in such a way that the module itself functions as a function. I’m not familiar enough with UMD to get anything meaningful out of inspecting the rollup-output.
The rest of this post is about a minimum example.
Here’s my very simple index.js:
// index.js
export {bar} from "./outer.js";
which is read by my rollup config:
// rollup.config.js
import npm from "rollup-plugin-node-resolve";
import commonjs from 'rollup-plugin-commonjs';
export default {
entry : "index.js",
format : "umd",
moduleName : "sphereModule",
plugins : [ npm({jsnext : true}), commonjs() ],
dest : "loadme.js"
};
I have a complete clonable repository demonstrating the problem.
Assigning directly to module.exports is basically equivalent to having a default export. Hence importing the module as follows should work:
import inner from "./inner.js";
Due to compatibility issues with typescript, babel, and webpack I have to use the export class Test {} syntax rather than export default class Test {}. It solves all of my issues with typescript but causes webpack to namespace everything on an object instead.
I'm having webpack generate umd and am testing the include via requirejs.
However, rather than passing in the function directly I'm now getting an object with a property instead. This won't fly in my real app.
{
Test: function Test() {}
}
webpack.config.js:
module.exports = {
entry: './test.js',
output: {
filename: 'a.js',
libraryTarget: 'umd'
},
module: {
loaders: [{
test: /\.js$/, loader: 'babel-loader'
}]
}
};
.babelrc:
{
"presets": ["es2015"]
}
I'm not sure if I've understood correctly, but after much experimentation I found the cleanest way to use modules in TypeScript is to simply use ES6 syntax in my source files.
// src/foo/Foo.ts
export class Foo {}
// src/bar/Bar.ts
import {Foo} from '../foo/Foo';
export class Bar extends Foo {}
This way your source files can remain agnostic to your output module format.
For large libraries, it's possible to keep an index.ts at the root of each of your "namespaces", which will afford you greater flexibility when exporting modules:
// src/widgets/FooWidget.ts
export class FooWidget {}
// src/widgets/BarWidget.ts
export class BarWidget {}
// src/widgets/index.ts
export * from './FooWidget';
export * from './BarWidget';
// src/index.ts
import * as widgets from './widgets';
import * as badgers from './badgers';
export {
widgets,
badgers
};
I need to do something like:
if (condition) {
import something from 'something';
}
// ...
if (something) {
something.doStuff();
}
The above code does not compile; it throws SyntaxError: ... 'import' and 'export' may only appear at the top level.
I tried using System.import as shown here, but I don't know where System comes from. Is it an ES6 proposal that didn't end up being accepted? The link to "programmatic API" from that article dumps me to a deprecated docs page.
We do have dynamic imports proposal now with ECMA. This is in stage 3. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (condition) {
import('something')
.then((something) => {
console.log(something.something);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also have other features like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
If you'd like, you could use require. This is a way to have a conditional require statement.
let something = null;
let other = null;
if (condition) {
something = require('something');
other = require('something').other;
}
if (something && other) {
something.doStuff();
other.doOtherStuff();
}
You can't import conditionally, but you can do the opposite: export something conditionally. It depends on your use case, so this work around might not be for you.
You can do:
api.js
import mockAPI from './mockAPI'
import realAPI from './realAPI'
const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI
apiConsumer.js
import API from './api'
...
I use that to mock analytics libs like mixpanel, etc... because I can't have multiple builds or our frontend currently. Not the most elegant, but works. I just have a few 'if' here and there depending on the environment because in the case of mixpanel, it needs initialization.
2020 Update
You can now call the import keyword as a function (i.e. import()) to load a module at runtime. It returns a Promise that resolves to an object with the module exports.
Example:
const mymodule = await import('modulename');
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
or:
import('modulename')
.then(mymodule => {
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
});
See Dynamic Imports on MDN
Looks like the answer is that, as of now, you can't.
http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api
I think the intent is to enable static analysis as much as possible, and conditionally imported modules break that. Also worth mentioning -- I'm using Babel, and I'm guessing that System is not supported by Babel because the module loader API didn't become an ES6 standard.
Import and Export Conditionally in JS
const value = (
await import(`${condtion ? `./file1.js` : `./file2.js`}`)
).default
export default value
Important difference if you use dynamic import Webpack mode eager:
if (normalCondition) {
// this will be included to bundle, whether you use it or not
import(...);
}
if (process.env.SOMETHING === 'true') {
// this will not be included to bundle, if SOMETHING is not 'true'
import(...);
}
require() is a way to import some module on the run time and it equally qualifies for static analysis like import if used with string literal paths. This is required by bundler to pick dependencies for the bundle.
const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;
For dynamic module resolution with complete static analysis support, first index modules in an indexer(index.js) and import indexer in host module.
// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';
// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
obscuring it in an eval worked for me, hiding it from the static analyzer ...
if (typeof __CLI__ !== 'undefined') {
eval("require('fs');")
}
Conditional imports could also be achieved with a ternary and require()s:
const logger = DEBUG ? require('dev-logger') : require('logger');
This example was taken from the ES Lint global-require docs: https://eslint.org/docs/rules/global-require
I was able to achieve this using an immediately-invoked function and require statement.
const something = (() => (
condition ? require('something') : null
))();
if(something) {
something.doStuff();
}
Look at this example for clear understanding of how dynamic import works.
Dynamic Module Imports Example
To have Basic Understanding of importing and exporting Modules.
JavaScript modules Github
Javascript Modules MDN
No, you can't!
However, having bumped into that issue should make you rethink on how you organize your code.
Before ES6 modules, we had CommonJS modules which used the require() syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code. - source: https://bitsofco.de/what-is-tree-shaking/
I guess one of the reasons they dropped that support on ES6 onward is the fact that compiling it would be very difficult or impossible.
One can go through the below link to learn more about dynamic imports
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports
I know this is not what the question is asking for, but here is my approach to use mocks when using vite. I'm sure we can do the same with webpack and others.
Suppose we have two libraries with same interface: link.js and link-mock.js, then:
In my vite.config.js
export default defineConfig(({ command, mode }) => {
const cfg = {/* ... */}
if (process.env.VITE_MOCK == 1) {
cfg.resolve.alias["./link"] = "./link-mock"; // magic is here!
}
return cfg;
}
code:
import { link } from "./link";
in console we call:
# to use the real link.js
npm run vite
# to use the mock link-mock.js
VITE_MOCK=1 npm run vite
or
package.json script
{
....
"scripts": {
"dev": "vite",
"dev-mock": "VITE_MOCK=1 vite"
}
}