Dynamic es6 module import names [duplicate] - dynamic

Is it possible to import something into a module providing a variable name while using ES6 import?
I.e. I want to import some module at a runtime depending on values provided in a config:
import something from './utils/' + variableName;
Note that I’m using Node.js, but answers must take compatibility with ECMAScript modules into consideration.

Not with the import statement. import and export are defined in such a way that they are statically analyzable, so they cannot depend on runtime information.
You are looking for the loader API (polyfill), but I'm a bit unclear about the status of the specification:
System.import('./utils/' + variableName).then(function(m) {
console.log(m);
});

Whilst this is not actually a dynamic import (eg in my circumstance, all the files I'm importing below will be imported and bundled by webpack, not selected at runtime), a pattern I've been using which may assist in some circumstances is:
import Template1 from './Template1.js';
import Template2 from './Template2.js';
const templates = {
Template1,
Template2
};
export function getTemplate (name) {
return templates[name];
}
or alternatively:
// index.js
export { default as Template1 } from './Template1';
export { default as Template2 } from './Template2';
// OtherComponent.js
import * as templates from './index.js'
...
// handy to be able to fall back to a default!
return templates[name] || templates.Template1;
I don't think I can fall back to a default as easily with require(), which throws an error if I try to import a constructed template path that doesn't exist.
Good examples and comparisons between require and import can be found here: http://www.2ality.com/2014/09/es6-modules-final.html
Excellent documentation on re-exporting from #iainastacio:
http://exploringjs.com/es6/ch_modules.html#sec_all-exporting-styles
I'm interested to hear feedback on this approach :)

There is a new specification which is called a dynamic import for ES modules.
Basically, you just call import('./path/file.js') and you're good to go. The function returns a promise, which resolves with the module if the import was successful.
async function importModule() {
try {
const module = await import('./path/module.js');
} catch (error) {
console.error('import failed');
}
}
Use cases
Use-cases include route based component importing for React, Vue etc and the ability to lazy load modules, once they are required during runtime.
Further Information
Here's is an explanation on Google Developers.
Browser compatibility (April 2020)
According to MDN it is supported by every current major browser (except IE) and caniuse.com shows 87% support across the global market share. Again no support in IE or non-chromium Edge.

In addition to Felix's answer, I'll note explicitly that this is not currently allowed by the ECMAScript 6 grammar:
ImportDeclaration :
import ImportClause FromClause ;
import ModuleSpecifier ;
FromClause :
from ModuleSpecifier
ModuleSpecifier :
StringLiteral
A ModuleSpecifier can only be a StringLiteral, not any other kind of expression like an AdditiveExpression.

I understand the question specifically asked for ES6 import in Node.js, but the following might help others looking for a more generic solution:
let variableName = "es5.js";
const something = require(`./utils/${variableName}`);
Note if you're importing an ES6 module and need to access the default export, you will need to use one of the following:
let variableName = "es6.js";
// Assigning
const defaultMethod = require(`./utils/${variableName}`).default;
// Accessing
const something = require(`./utils/${variableName}`);
something.default();
You can also use destructuring with this approach which may add more syntax familiarity with your other imports:
// Destructuring
const { someMethod } = require(`./utils/${variableName}`);
someMethod();
Unfortunately, if you want to access default as well as destructuring, you will need to perform this in multiple steps:
// ES6 Syntax
Import defaultMethod, { someMethod } from "const-path.js";
// Destructuring + default assignment
const something = require(`./utils/${variableName}`);
const defaultMethod = something.default;
const { someMethod, someOtherMethod } = something;

you can use the non-ES6 notation to do that. this is what worked for me:
let myModule = null;
if (needsToLoadModule) {
myModule = require('my-module').default;
}

I had similar problem using Vue.js: When you use variable in import(variableName) at build time Webpack doesn't know where to looking for. So you have to restrict it to known path with propriate extension like that:
let something = import("#/" + variableName + ".js")
That answer in github for the same issue was very helpful for me.

I less like this syntax, but it work:
instead of writing
import memberName from "path" + "fileName";
// this will not work!, since "path" + "fileName" need to be string literal
use this syntax:
let memberName = require("path" + "fileName");

Dynamic import() (available in Chrome 63+) will do your job. Here's how:
let variableName = 'test.js';
let utilsPath = './utils/' + variableName;
import(utilsPath).then((module) => { module.something(); });

./utils/test.js
export default () => {
doSomething...
}
call from file
const variableName = 'test';
const package = require(`./utils/${variableName}`);
package.default();

I would do it like this
function load(filePath) {
return () => System.import(`${filePath}.js`);
// Note: Change .js to your file extension
}
let A = load('./utils/' + variableName)
// Now you can use A in your module

It depends. You can use template literals in dynamic imports to import a file based on a variable.
I used dynamic imports to add .vue files to vue router. I have excluded the Home.vue view import.
const pages = [
'About',
['About', 'Team'],
]
const nodes = [
{
name: 'Home',
path: '/',
component: Home,
}
]
for (const page of pages) {
if (typeof page === 'string') {
nodes.push({
name: page,
path: `/${page}`,
component: import(`./views/${page}.vue`),
})
} else {
nodes.push({
name: _.last(page),
path: `/${page.join('/')}`,
component: import(`./views/${_.last(page)}.vue`)
})
}
}
This worked for me. I was using yarn + vite + vue on replit.

Related

Docusaurus: How can I have multiple versions of different docs in the docs directory?

I'm working with Docusaurus to create a documentation site for 3 different education courses - all within the docs folder.
So I'm looking for a way to have the version be different across folders in there, or figure out what the best strategy for this is.
Right now, in my docusaurus.config.js I have:
module.exports = {
presets: [
'#docusaurus/preset-classic',
docs: {
lastVersion: 'current',
versions: {
current: {
label: '1.0.0',
path: '1.0.0',
},
},
},
],
};
But I'm not sure how to keep track of 3 different versions across 3 different docs all within the same site.
Swizzle the navbar via wrapping
yarn run swizzle #docusaurus/theme-classic NavbarItem/DocsVersionDropdownNavbarItem -- --wrap
Modify the swizzled component like so:
src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js:
import React from "react";
import DocsVersionDropdownNavbarItem from '#theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
import { useLocation } from '#docusaurus/router';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const { docsPluginId, className, type } = props
const { pathname } = useLocation()
/* (Custom) check if docsPluginId contains pathname
Given that the docsPluginId is 'charge-controller' and the routeBasePath is 'charge-controller', we can check against the current URI (pathname).
If the pathname contains the docsPluginId, we want to show the version dropdown. Otherwise, we don't want to show it.
This gives us one, global, context-aware version dropdown that works with multi-instance setups.
You want to declare a version dropdown for each plugin in your navbarItems config property for this to work well.
const doesPathnameContainDocsPluginId = pathname.includes(docsPluginId)
if (!doesPathnameContainDocsPluginId) {
return null
}
return <DocsVersionDropdownNavbarItem {...props} />;
}
For this to work, you need to have your documentation (based on products) split up using multi-instances: (https://docusaurus.io/docs/docs-multi-instance#docs-navbar-items)
Note that the preset docsPlugin ID always is "default".
You can try to use
import {
useActivePluginAndVersion,
} from '#docusaurus/plugin-content-docs/client';
const version = activePluginAndVersion.activeVersion.name; // use label instead of name if issues arise.
instead to get the current docsPluginId, name or label.
This would be the more "robust" solution I think. That said, we do use the solution I provided above as-is and it works fine for now.

Add CoreUI icon to DevExtreme dxDataGrid column header in Vue.js

Currently I am working on a new UI for a legacy API. Unfortunately, this one delivers HTML source code for a column header. This code usually creates a FontAwesome icon. This library will not be used in the new project.
I found a very similar icon in the Icon Library of CoreUI. Now it is only a matter of rendering the icon at this point. However, no approach has been successful so far. How can I replace the icon in the headerCellTemplate method?
Or maybe there is a completely different, much better approach to do this. I don't know if I am on the right track with this method approach. You can probably use static templates, but I don't know how to do that.
import { CIcon } from '#coreui/vue';
import { cilCheckCircle } from '#coreui/icons';
headerCellTemplate: (element, info) => {
element.innerHTML = curr.ColumnTitle;
if (element.firstChild.nodeName === 'I') {
// WORKS
//element.firstChild.innerHTML = 'Done';
// ANOTHER EXPERIMENT
//const componentClass = Vue.extend(cilCheckCircle);
//const instance = new componentClass();
//instance.$mount();
//element.removeChild(element.firstChild);
//element.appendChild(instance.$el);
// ALSO NOT WORKING
return CIcon.render.call(this, cilCheckCircle);
}
}
I finally found a solution after revisiting this interesting article.
import Vue from 'vue';
import { CIcon } from '#coreui/vue';
import { cilCheckCircle } from '#coreui/icons';
headerCellTemplate: (element, info) => {
element.innerHTML = curr.ColumnTitle;
if (element.firstChild.nodeName === 'I') {
const cIconClass = Vue.extend(CIcon);
const instance = new cIconClass({
propsData: { content: cilCheckCircle }
});
instance.$mount(element.firstChild);
}
}
I don't know, though, if this is the ideal solution. So feel free to tell me, if you have a better, less complex solution.

jest snapshot testing: how to ignore part of the snapshot file in jest test results

Problem: ignore some part of the .snap file test results
the question here: there are some components in my test that have a random values and i don't really care about testing them. is there any way to ignore part of my X.snap file? so when i run tests in the future it won't give me test fail results.
Now you can also use property matcher for these cases.
By example to be able to use snapshot with these object :
const obj = {
id: dynamic(),
foo: 'bar',
other: 'value',
val: 1,
};
You can use :
expect(obj).toMatchSnapshot({
id: expect.any(String),
});
Jest will just check that id is a String and will process the other fields in the snapshot as usual.
Actually, you need to mock the moving parts.
As stated in jest docs:
Your tests should be deterministic. That is, running the same tests multiple times on a component that has not changed should produce the same results every time. You're responsible for making sure your generated snapshots do not include platform specific or other non-deterministic data.
If it's something related to time, you could use
Date.now = jest.fn(() => 1482363367071);
I know it's quite old question but I know one more solution. You can modify property you want to ignore, so it will be always constant instead of random / dynamic. This is best for cases when you are using third party code and thus may not be able to control the non deterministic property generation
Example:
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Card from './Card';
import toJSON from 'enzyme-to-json';
Enzyme.configure({ adapter: new Adapter() });
describe('<Card />', () => {
it('renders <Card /> component', () => {
const card = shallow(
<Card
baseChance={1}
name={`test name`}
description={`long description`}
imageURL={'https://d2ph5fj80uercy.cloudfront.net/03/cat1425.jpg'}
id={0}
canBeIgnored={false}
isPassive={false}
/>
);
const snapshot = toJSON(card);
// for some reason snapshot.node.props.style.backgroundColor = "#cfc5f6"
// does not work, seems the prop is being set later
Object.defineProperty(snapshot.node.props.style, 'backgroundColor', { value: "#cfc5f6", writable: false });
// second expect statement is enaugh but this is the prop we care about:
expect(snapshot.node.props.style.backgroundColor).toBe("#cfc5f6");
expect(snapshot).toMatchSnapshot();
});
});
You can ignore some parts in the snapshot tests replacing the properties in the HTML. Using jest with testing-library, it would look something like this:
it('should match snapshot', async () => {
expect(removeUnstableHtmlProperties(await screen.findByTestId('main-container'))).toMatchSnapshot();
});
function removeUnstableHtmlProperties(htmlElement: HTMLElement) {
const domHTML = prettyDOM(htmlElement, Infinity);
if (!domHTML) return undefined;
return domHTML.replace(/id(.*)"(.*)"/g, '');
}
I used this to override moment's fromNow to make my snapshots deterministic:
import moment, {Moment} from "moment";
moment.fn.fromNow = jest.fn(function (this: Moment) {
const withoutSuffix = false;
return this.from(moment("2023-01-12T20:14:00"), withoutSuffix);
});

Can import be used in Jakefiles?

My editor is complaining that i.e. task is undefined, because it just treats Jakefiles as JavaScript.
Can I do something like this?
const { task } = require('jake')
task('default', () => {
...
(Also, is Jakefile the correct filename to use?)
Tried it in Node.js v6, and it works.

Aurelia validationjs

For the life of me I can't seem to get it working.
github project here
Is this because the aurelia-validationjs project is still in flux?
Would love to get declarative validations reflecting on the UI. Tried fluent validations as well, but to no avail.
Managed to get aurelia validation working after following the latest post updates from http://blog.durandal.io/2016/06/14/new-validation-alpha-is-here/
I'll be updating the linked github project for anyone interested in the code.
The aurelia-validation plugin has been rewritten recently and the validation API has changed again with respect to the accepted answer.
It is now using 2 separate libraries aurelia-validation and aurelia-validatejs. Validators do not seem to exist anymore and have been replaced by ValidationControllers.
The new API description and some examples can be found here:
http://blog.durandal.io/2016/06/14/new-validation-alpha-is-here/
.... and a working gist can be found here:
https://gist.run/?id=381fdb1a4b0865a4c25026187db865ce
The usage can be summarized in the code below:
import {inject, NewInstance} from 'aurelia-dependency-injection';
import {ValidationController, validateTrigger} from 'aurelia-validation';
import {required, email, ValidationRules} from 'aurelia-validatejs';
#inject(NewInstance.of(ValidationController))
export class RegistrationForm {
firstName = '';
lastName = '';
email = '';
constructor(controller) {
this.controller = controller;
// the default mode is validateTrigger.blur but
// you can change it:
// controller.validateTrigger = validateTrigger.manual;
// controller.validateTrigger = validateTrigger.change;
}
submit() {
let errors = this.controller.validate();
// todo: call server...
}
reset() {
this.firstName = '';
this.lastName = '';
this.email = '';
this.controller.reset();
}
}
ValidationRules
.ensure('firstName').required()
.ensure('lastName').required()
.ensure('email').required().email()
.on(RegistrationForm);
You can also have a look at my repo in the following link:
https://github.com/alexandre-spieser/AureliaAspNetCoreAuth
In the login.js I use both aurelia-validation and aurelia-validatejs....
and bootstrap-validation is made available globally in my main.js file:
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-animator-css')
.plugin('aurelia-validation')
.plugin('aurelia-validatejs')
.feature('bootstrap-validation'); // <= here
aurelia.start().then(() => aurelia.setRoot());
}
The validation renderer can be seen here:
https://github.com/alexandre-spieser/AureliaAspNetCoreAuth/tree/master/src/AureliaAspNetCoreAuth/wwwroot/src/bootstrap-validation