How to make Webpack recognize dynamic exports - javascript

I'm seeing the following warning when building with Webpack v4 (using babel-loader for the JS files):
Warning in ./src/components/Foo
"export 'ADDENDUM' was not found in '../../types'
...
The import in ./src/components/Foo is:
import { ADDENDUM } from '../../types';
../../types:
import { each } from 'lodash';
export const typesDict = {
ADDENDUM: 'addendum',
};
each(typesDict, (type, typeConstant) => {
exports[typeConstant] = type;
});
This isn't causing a build error, just a warning. The warning is wrong though, since I am exporting ADDENDUM (though dynamically), and everything works as it should.
Is there a way for Webpack to handle these dynamic imports, or to at least turn off the warning? I'm upgrading from Webpack v1 right now, and v1 does not have this problem (or if it does, it's being hidden somehow).
Also please note: I do NOT want to silence all Webpack warnings, such as via the devServer config. I just want to silence this one type of warning.

Based on your ../../types file i assume your approach was to skip writing again the components in the exports object.
Instead of silencing the warning, try something simpler to fix the issue. Since you don't want to write twice the same names, take a look at my example.
No lodash is required, no loops are used and exported constants are written once.
../../types:
export const ADDENDUM = 'addendum';
export const ADDENDUM2 = 'addendum2';
export const ADDENDUM3 = 'addendum3';
That is all, no more dynamic imports, no more warnings.
UPDATE:
Your code is indeed valid, but when you use dynamic exports/imports, compilers/bundlers loose trace of your exports(in your case) since they don't check the contents of your exports object, thus the warning you receive, because the compiler(babel) didn't find exports.ADDENDUM in your code, only you know that it's there, therefore the compiler thinks you're using an unexisting component.
As of imports, it's the same story, the same type of warning was emitted by webpack when something like require('/path/to/' + someVar + '/some.file.js'), because webpack wanted to make a chunk out of it, but that wasn't a full path for webpack and it couldn't find the file because it was a concatenated string (dynamic import). (i don't know if this changed over the years, but i'm sure you understand/knew this perfectly well too)

Related

Working with native dynamic ES2020 modules in TypeScript (without Node.js) and typing things

I have a TypeScript application working with es16 modules, most of them are imported statically. I want to use a (validator-) module now that is only imported in debug mode. It's all working, but I don't understand how to type things so that I get code completion and error-checking.
in my main class in main.ts I have:
...
if(debug){
import('./validator.js').then((module) => this.validate(module))
}
...
the validate method looks like that:
private validate(module):void{
new module.Validator(dataToValidate);
}
validator.js contains:
export class Validator{
coonstructor(data:MyDatatype){
stuff going on here...
}
}
what I would like to know/do is:
in the validate method:
private validate(module:someMeaningfulType){...}
and also I'd like to import the Validator class, without actually importing it.
if I wrote
import {Validator} from './validate.ts'
at the top of main.ts I would load the file regardles of I need it, which defeats the whole point of dynamic imports.
I might try to whrite a type declartaion for module and Validator in main.ts, but even if that wouldn't conflict somehow, I would have to manually keep it in sync with the actual module, which is not what I want - obviously.
I might miss something obvious, but I cannot find out what. I find id hard to search for the (pure) use of native es2020/2022 modules with Typescrit, as there is so much information about node-modules etc. overshadowing it.
You can actually use import with typeof to get the type of the imported module:
private validate(module: typeof import("./validator.js")) { ... }
Alternatively, you can use a type-only import, which will be erased in the output:
import type * as ValidatorModule from "./validator.js";
// ...
private validate(module: ValidatorModule) { ... }

Get past import/no-named-as-default lint error without config changes

I am getting no-named-as-default eslint error.
Having issues where I need Redux functionality and I can't just go for named imports.
Seen similar queries here but they all involve config changes like amending the eslint file
or things to do with Babel configs.
Example: How do I resolve eslint import/no-named-as-default
Is there a way to achieve it in code itself?
Cos I can't change any of the configs. I am ok to forgo default import all together and use 2x named imports (maybe 1 with an alias?).
But I can't seem to do away with default. The bottom export complains when I remove it.
Also unsure if eslint will start complaining if there is no default imports on a Component.
This is the component which the eslint complains about if I import this as default.
export const IndicatorPage = ({setData}) => {
// logic
}
const mapDispatchToProps = {
setData: setIndicatorData
}
// ide complains if I remove this default. Plus even if I could drop default,
// wondering if that would be another lint error for not having defaults
export default connect(null, mapDispatchToProps)(IndicatorPage);
Example when I import this in another Component.
This makes eslint happy but it breaks my redux functionality.
import { IndicatorPage } from './IndicatorPage';
This makes Redux work properly but now eslint complains...
import IndicatorPage from './IndicatorPage';

How can I reload an ES6 module at runtime?

Prior to ES6 modules, it was (I'm told by other Stack answers) easy to force a JS script to be reloaded, by deleting its require cache:
delete require.cache[require.resolve('./mymodule.js')]
However, I can't find an equivalent for ES6 modules loaded via import.
That might be enough to make this question clear, but just in case, here's a simplified version of the code. What I have is a node server running something like:
-- look.mjs --
var look = function(user) { console.log(user + " looks arond.") }
export { look };
-- parser.mjs --
import { look } from './look.mjs';
function parse(user, str) {
if (str == "look") return look(user);
}
What I want is to be able to manually change the look.mjs file (e.g. to fix a misspelled word), trigger a function that causes look.mjs to be reimported during runtime, such that parse() returns the new value without having to restart the node server.
I tried changing to dynamic import, like this:
-- parser.mjs --
function parse(user, str) {
if (str == "look") {
import('./look.mjs').then(m => m.look(user))
}
}
This doesn't work either. (I mean, it does, but it doesn't reload look.mjs each time it's called, just on the first time) And I'd prefer to keep using static imports if possible.
Also, in case this is not clear, this is all server side. I'm not trying to pass a new module to the client, just get one node module to reload another node module.
I don't know what the reason behind doing this,
I think this is not safe to change the context of modules at runtime and cause unexpected behaviors and this is one of the reasons that Deno came to.
If you want to run some code evaluation at runtime you can use something like this using vm:
https://nodejs.org/dist/latest-v16.x/docs/api/vm.html
You could try using nodemon to dynamically refresh when you make code changes
https://www.npmjs.com/package/nodemon
I agree with #tarek-salem that it's better to use vm library. But there is another way to solve your problem.
There is no way to clear the dynamic import cache which you use in question (btw there is a way to clear the common import cache because require and common import has the same cache and the dynamic import has its own cache). But you can use require instead of dynamic import. To do it first create require in parser.mjs
import Module from "module";
const require = Module.createRequire(import.meta.url);
Then you have 2 options:
Easier: convert look.mjs into commonjs format (rename it look.cjs and use module.exports).
If want to make it possible to either import AND require look.mjs you should create the npm package with package.json
{
"main": "./look.cjs",
"type": "commonjs"
}
In this case in parser.mjs you will be able to use require('look') and in other files import('look') or import * as look from 'look'.

Babel transform imports to destructure default

I'm trying to create a build pipeline which doesn't bundle the files together, but instead uses <script type="module">. This will let me just recompile files as they change without rebundling, greatly improving build times during development.
Our project uses ES6, so this is generally easy.
There is however one snag: third-party modules that only have CommonJS builds (such as react).
There are a few ways around this. For now, I have a transform that changes the import name from react to /node_modules/react and my server is smart enough to then go find the appropriate dist file from node_modules and serve it up. This all works fine.
The problem is that it gets confused when I try to do something like:
import { Component } from 'react';
That won't work how it currently is (because it gets confused by there not being a default). However, this will work:
import * as React from 'react';
const { Component } = React;
I could manually do this for all files and packages, but a) that would make it unnecessarily ugly (with Redux and other things, there are half a dozen different packages in many files we'd have to do this to and b) there are lots of files, I don't want to manually change them all.
Is there a Babel transform plugin that can automatically make this kind of conversion? It seems that this isn't a completely novel approach, so I'm hoping there is a plugin that'll do it for me that my Google-fu failed to find.
I managed to get this working. I ended up having to write my own Babel plugin which makes the changes that I was looking for. Basically, for the different versions of the import statement, it changes it around in order to work better with UMD and CJS modules.
For example, something like this:
import A, { a, b, c } from 'a';
was transformed into something like this:
import * from __babel_A from 'a';
const A = __babel_A.default && (__babel_A.default.default || __babel_A.default) || __babel_A;
const { a, b, c } = __babel_A.default || __babel_A;
This format came about based on the way most things export. Many (like React) would put everything into an object with it's name on either module.exports (if I provided a fake one) or this or window. Others ended up not grouping things together (like ReactRTE) and had its own "default", which is where the default.default bit came from.
In addition to this transform, when my server serves up the dist versions of the third-party files, it'd wrap them up in a way that would then let me do an export default. That looked like this:
const module = {};
const keys = Object.keys(this || window);
const toExport = (function __auto_wrapper() {
${fileCode}
return module.exports || Object.keys(this).reduce((r, k) => keys.includes(k) ? r : Object.assign(r, { [k]: this[k].default || this[k] }), {});
}).call(this || window);
export default Object.keys(toExport).length === 1 ? Object.entries(toExport)[0][1] : toExport;
Again, the different ways are based on what the different projects output. Some will use module.exports when given it, others won't consider module.exports a real thing since I (purposely) didn't initialize export as an object (if I did, it'd try to use require() which I didn't want). In my case, ReactRTE had just a CJS module instead of an UMD, so I also had to do replace on its code to replace require('react') and require('react-dom') with references to the objects on window instead.
This all works as I wanted (completely unbundled code loading in the browser). The only slight side-effect is that React and friends are all available on window (which they normally wouldn't be if bundled properly), but that's pretty minor.

Babel compiles constant to undefined

With this code
const noCurrencies: Map<CurrencyCode, Currency> = new Map();
/**
* Reducer for the finished currency request.
*/
export function currencies(state: Map<CurrencyCode, Currency> = noCurrencies, action: Action): Map<CurrencyCode, Currency> {
switch (action.type) {
case SET_CURRENCIES:
return action.currencies;
default:
console.log('currencies(reducer): noCurrencies', noCurrencies)
return state;
}
}
I get this console output:
currencies(reducer): noCurrencies undefined
Is this a known problem with Babel? How do I debug it? I have a feeling that it's due to this particular file having been called twice during initialisation and thus having a circular dependency with another file.
(I'm not 'recreating a repro from scratch', so don't suggest that, and the types are https://github.com/flowtype/flow-typed which get removed in a pre-processor step, and I've tried without types as well, with the same result)
The reason was a large circular dependency between modules. Even if using ECMAScript module syntax, the compiler (WebPack 3 + babel) is not smart enough to only load what is needed when it's needed; but instead evaluate all exports and imports whenever a file is used/touched.
That means that if you have index.js files that liberally export things from around them, to create a single entry-point for callers outside its folder, you'll likely run into issues like this.
I had to go through the code-base and make the imports 'deep' by directing imports straight to the, of the index file, surrounding files, instead.
E.g.
folder A
epics.js
commands.js
index.js
types.js
actions.js
reducers.js
messages.js
...
folder B
import { create } from '../A' becomes import { create } from '../A/types'
and so forth for all things; in the end I'm only exporting React views and types from index.js

Categories