Customize the way “import” works with webpack - javascript

I need to customize the way webpack is handling imports in my app.
Some of my services have mock implementations.
In test mode, I want to import mock file instead of real service if a file with ‘.mock’ postfix exists next to the service, otherwise imports the service itself.
Please note that I need different output files (‘main.js’ and ‘test.js’). So I need to make sure test.js is not including the real services implementations (preventing execution is not enough, the source should not be imported at all).
Services folder contains following files:
service-1.js
service-1.mock.js
service-2.js
index.js
services/index.js:
import service1 from ‘./service-1’
import service2 from ‘./service-2’
export {service1, service2}
Please advise how can I config my webpack.

According to the comments I can suggest these workarounds using custom loader:
Method #1
Create one .mobile and .desktop for each of your components along the main file (e.g: component.js, component.mobile.js, component.desktop.js) and use this custom loader:
const targets = {
desktop: 'desktop',
mobile: 'mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header';
import Footer from './shared/about';
import Categories from './category/categories';
// rest of code
`;
const manipulated = manipulateSource(source, targets.mobile, ['components', 'shared']);
console.log(manipulated);
function manipulateSource(src, target = targets.desktop, pathMatches = []) {
const paths = pathMatches.length ? `(${pathMatches.join('|')})` : '';
const pattern = new RegExp(`(?<left>.*import.*${paths}.*\\\/)(?<name>[\\w\\-_]*)(?<rest>.*\\n)`, 'g');
const manipulated = src.replace(pattern, (...args) => {
const [{
left,
name,
rest
}] = args.slice(-1);
return `${left}${name}.${target}${rest}`;
});
return manipulated;
}
Method #2
For those files has different implementations for .mobile and .desktop create the third file (or fourth in case you want to put shareable code in main file) with same name and a meaningful extension (e.g: component.platformAdaptive.js) which can be handled with regular expresion (or any other way of manipulation). In this method you may need to put basic implementation in last file in case you use strongTypes (e.g: Typescript):
const targets = {
desktop: 'desktop',
mobile: 'mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header.platformAdaptive';
import Footer from './shared/about.platformAdaptive';
import Categories from './category/categories.platformAdaptive';
// rest of code
`;
const manipulatedMob = manipulateSource(source, 'platformAdaptive', targets.mobile);
const manipulatedDesk = manipulateSource(source, 'platformAdaptive');
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, replace, target = targets.desktop) {
const pattern = new RegExp(`(?<left>.*\\\/)(?<name>[\\w\\-_]*\.)${replace}(?<rest>.*\\n)`, 'g');
const manipulated = src.replace(pattern, (...args) => {
const [{
left,
name,
rest
}] = args.slice(-1);
return `${left}${name}${target}${rest}`;
});
return manipulated;
}
Both of the above methods come with some restrictions in imports, like you can't use Barrel files (index.js) since they assume last chunk of the import is the component file.
In this case you can add multiple folders with a barrel to handle those imports. for example in second method you'll need such structure:
|-- components.platformAdaptive
|-- index.js
|-- components.mobile
|-- index.js
|-- components.desktop
|-- index.js
Or You can use / instead of . to create a nested structure (e.g: components/platformAdaptive):
|-- components
|-- [+] platformAdaptive
|-- [+] mobile
|-- [+] desktop
Method #3
Another way to handle this situation would be to have different classes with different names. For example, a List component with different implementations for mobile and desktop then there would be three components like ListPlatformAdaptive, ListMobile, ListDesktop - in which the ListPlatformAdaptive may has the basic implementations - and a barrel in component folder which exports the components:
import * as ListPlatformAdaptive from './list.platformAdaptive';
import * as ListMobile from './list.mobile';
import * as ListDesktop from './list.desktop';
export {
ListPlatformAdaptive,
ListMobile,
ListDesktop
}
The structure would be like this:
|-- components
|-- list.platformAdaptive.js
|-- list.mobile.js
|-- list.desktop.js
|-- index.js
Then manipulation would be like this:
const targets = {
desktop: 'Desktop',
mobile: 'Mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import HeaderPlatformAdaptive as Header from './shared/Header';
import FooterPlatformAdaptive as Footer from './shared/about';
import CategoriesPlatformAdaptive as Categories from './category/categories';
// rest of code
`;
const replace = 'PlatformAdaptive';
const manipulatedMob = manipulateSource(source, replace, targets.mobile);
const manipulatedDesk = manipulateSource(source, replace);
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, replace, target = targets.desktop) {
const pattern = new RegExp(replace, 'g');
const manipulated = src.replace(pattern, target);
return manipulated;
}
I this method you should be careful about the barrel files to be excluded and the downside of this method is that all of the components have been imported already therefore import cost is not acceptable.
Method #4
Another way I can think of is to add some notes as comment and react againts its existance in that line:
const targets = {
desktop: 'Desktop',
mobile: 'Mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header'; /* #adaptive */
import Footer from './shared/about'; /* #adaptive: Desktop */
import Categories from './category/categories'; /* #adaptive: Mobile */
// rest of code
`;
const manipulatedMob = manipulateSource(source, targets.mobile);
const manipulatedDesk = manipulateSource(source);
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, targetDevice = targets.desktop) {
const pattern = /(?<left>.*import\s+)(?<name>\w+)(?<rest1>.*)\#adaptive(\:\s*(?<target>\w+))?(?<rest2>.*)/g
const manipulated = src.replace(pattern, (matched, ...args) => {
let [{
left,
name,
rest1,
target,
rest2
}] = args.slice(-1);
target = target || targetDevice;
return target == targetDevice ?
`${left}${name}${target}$ as ${name}${rest1}${rest2}` :
matched;
});
return manipulated;
}
In this method like method #2 imported components names are different than the original but mapped to original name which is not good at all, but I like it the most since if using it in barrel files and it's possible to change imported file address. Another fun part can be to pass target files address with respect to target device and parse it.
Conclusion
As you can see all of my answers were about handle sources without checking the existence of the file and assumes the developer is sure about it. BTW, you can search to see if there is anyway to find file absolute path and then check availability of targeted substitutes.

Related

Why can I not use a dynamic import when using Nuxt3

I am trying to create a component that will let me pass in the name of the icon I am looking for and import that icon dynamically via unplugin-icons, it seems to be not working whenever the path for the import is dynamic. If I were to just type a string it works fine. Any suggestions? I am using Nuxt 3 and Vite.
interface Icon {
name: string;
}
const props = defineProps<Icon>();
const sun = "sun";
const icon = await import(`~icons/fa-solid/${sun}`);
const PropIcon = defineComponent(icon.default);
Below is the error I recieve
const props = __props;
12 | const sun = "sun";
13 | const icon = ([__temp, __restore] = _withAsyncContext(() => import(`~icons/fa-solid/${sun}`)), __temp = await __temp, __restore(), __temp);
| ^
14 | const PropIcon = defineComponent(icon.default);
15 | const __returned__ = { props, sun, icon, PropIcon };
The above dynamic import cannot be analyzed by vite.
See https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations for supported dynamic import formats. If this is intended to be left as-is, you can use the /* #vite-ignore */ comment inside the import() call to suppress this warning.
I tried to use the /* vite ignore */ but it did not work
Below is the hardcoded version which works
interface Icon {
name: string;
}
const props = defineProps<Icon>();
const sun = "sun";
const icon = await import(`~icons/fa-solid/sun`);
const PropIcon = defineComponent(icon.default);
Below is the standard import
import IconSun from "~icons/fa-solid/sun";
Well vite already give you the answer just read it:
If this is intended to be left as-is, you can use the /* #vite-ignore
*/ comment inside the import() call to suppress this warning.
Vite cannot analyze the importing module because it cant know the value of sun
You need to surpress this error if this is intended:
const icon = await import( /* #vite-ignore */` ~icons/fa-solid/${sun}`);

Can I move JS code out of Svelte component file to other file js file?

I am currently exploring Bucklescript/ReasonML with Svelte 3 for my next project. Typical Svelte component is a .svelte file:
<script>
let name = 'world';
</script>
<h1>Hello world!</h1>
Instead, can I have script tag with src or equivalent to keep JS code in a separate file?
<script src='./some-file.js'></script>
By moving the js code to a separate file, the target of the Bucklescript compiler (which is a JS file) could be used for the component.
Vue.js already supports this with their SFC .vue file.
On a side note: I could use Vue.js for this but the presence Virtual DOM is problematic for legacy codebase. And, Svelte is diminishing at runtime and thus very much desirable. Also, the use this in Vue makes things awkward in Ocaml/Reason.
This is possible with the svelte.preprocess API, which you'd most commonly reach via the preprocess option in rollup-plugin-svelte or svelte-loader. Something like this should work (though I haven't tested it):
const path = require( 'path' )
const fs = require( 'fs' )
...
plugins: [
svelte({
// ...
preprocess: {
script: ({ content, attributes, filename }) => {
if ( 'string' === typeof attributes.src ) {
const file = path.resolve(path.dirname(filename), attributes.src);
const code = fs.readFileSync(file, 'utf-8');
return {code, dependencies: [file]};
}
}
}
})
]
As far as I know, this isn't possible right now.
What you could do is export everything you need from the js file, and then import them in the component: https://svelte.dev/repl/1d630ff27a0c48d38e4762cf6b0c2da5?version=3.7.1
<script>
import { name } from './mycode.js'
</script>
<h1>Hello {name}!</h1>
export let name = 'World';
However that would only be a partial solution as any mutation of data occurring within the file would not trigger a re-render of the DOM, as Svelte would not compile the .js file and would not be able to add its own code invalidating the values: https://svelte.dev/repl/c4b41b769ed747acb01a02a9af33e545?version=3.7.1
<script>
import { name, handleClick } from './mycode.js'
</script>
<h1 on:click={handleClick}>Hello {name}!</h1>
export let name = 'World';
export const handleClick = () => {
name = 'Everyone';
}
But that doesn't mean you can't be tricky if you are willing to go the extra mile to achieve this: https://svelte.dev/repl/8e259df629614ac99cb14cfae2f30658?version=3.7.1
<script>
import { name, handleClick } from './mycode.js'
const onClick = () => {
handleClick();
name = name;
}
</script>
<h1 on:click={onClick}>Hello {name}!</h1>
export let name = 'World';
export const handleClick = () => {
name = 'Everyone';
}
The extra line name = name forcing the DOM update.

How to Export Variables with a Dynamic Names

I have a components folder in nuxt.js
/components/atoms/
and inside that folder I have an index.js to export all components dynamically
const req = require.context('./', true, /\.vue$/)
const components = {}
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
components[componentName] = req(fileName).default
})
export const { ButtonStyled, TextLead, InputSearch } = components
so I can import perfectly as I wish
import { ButtonStyled } from "#/components/atoms"
the problem is that I am defining the variables to be exported statically, fixed, so for each created component I would need to add another variable manually
I need to dynamically export the variable name
Example:
DynamicCreation = ['ButtonStyled', 'TextLead', 'InputSearch']
export const { DynamicCreation } = components
// output -> export const { ButtonStyled, TextLead,InputSearch } = components
I need to export the name of already unstructured variables
Note: I can not use this export default components because I can not import like this import { ButtonStyled } from "#/components/atoms"
You should be able to do it like this:
export default components
Then in your file where you want to use the components:
import * as components from '#/components/atoms'
Then when you need to use the components in your Vue files, you need to map them:
#Component({
components: {
ButtonStyled: components.ButtonStyled
}
})
And now you have:
<ButtonStyled></ButtonStyled>
You can make something like this way, check if is what do you need.
Create a file to import a conjunct of components: allComponents.js
export default {
componentOne: require('./passToOneComponent.js');
componentTwo: require('./passToOneComponent.js');
componentThree: require('./passToOneComponent.js');
}
After in index.js export the allComponents.js with the name that you wish:
export {default as SomeName } from 'allComponents.js';
So in the final file, you can make something like:
import { SomeName } from 'index.js';
SomeName.componentOne();
I created a library that does this type of export, anyone who wants can install via npm
I created a Webpack Plugin that makes named exports from a component, maybe this helps other people
Weback Plugin - named-exports

React - intl with external translation file

I'm trying to implement language support in the base version of a react app (create using npm create-react etc...)
I've installed react-intl but every time I search on google a guideline, I found a lot of not-working solutions.
What I want is using the classic <FormattedMessage id="myid" > in my component.
Messages are stored in a SEPARATE file called './locales/en.json' or './locales/en.js'.
In my app.js im using
import {addLocaleData, IntlProvider} from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
addLocaleData(enLocaleData);
and I used:
<IntlProvider locale='en' messages="i-dont-know-what-insert-here">
<Index />
</IntlProvider>
I've tried a lot of solution.
FIRST APPROACH:
en.json is built like this:
{
"base.user":"Username"
[...]
}
SECOND APPROACH:
en.js is build like this:
export const ENGLISH = {
lang: 'en',
messages: {
"base.user":"Username"
[...]
}
}
These are the example I found on google but I don't know how to use these files inside the app.
As next step, I have to allow the user to change language pressing a flag button but for now, I will happy to see translation files working.
Thanks.
I've found a solution so I will post here for others.
FIRST: I've used second approach with lang.js files.
So you have:
export const ENGLISH = {
lang: 'en',
messages: {
"base.user":"Username"
[...]
}
}
In your app.js or in your root.js where you are using IntlProvider, include the files with:
import { ENGLISH } from './locales/en';
In you app.js (or your root js) use this:
class App extends Component {
constructor(props)
{
super(props);
this.state = {
locale : 'en',
messages : ENGLISH.messages
}
this.changeLanguage = this.changeLanguage.bind(this);
}
and then:
<IntlProvider locale={this.state.locale} messages={this.state.messages}>
<Index changeLanguage = {this.changeLanguage} />
</IntlProvider>
You can see the changeLanguage. That is the second question I post above.
This is the changeLanguage method:
changeLanguage(lang)
{
switch(lang)
{
case "it": this.setState({local:lang}); this.setState({messages:ITALIAN.messages}); break;
default: this.setState({locale:'en'}); this.setState({messages:ENGLISH.messages}); break;
}
}
Passing the method to the Index component, allow it to change language using some sort of event.
In your child Component (in my case Index) you have to define a changeLanguage method mapped on father method:
changeLanguage = (lang) => {
this.props.changeLanguage(lang);
}
Finally, if you have a flag button (o something else) you can use:
onClick={ () => this.changeLanguage('it') }
React will reload component automatically so every
<FormattedMessage ...
you have, will be translated dinamically

Javascript es6 re-exports

EDIT
Maybe I found the problem, would like if someone could confirm the situation: It seems like test.js is importing index.js which is importing test.js, and export has not an stop infinite-loop inclusion... Is it right? Is there any workaround like Don't include this file if it's the calling one?
I'm facing a strange problem. I'm trying to import some objects from a re-export (tcomb-react-native is not relevant here as the problem is with import/export).
|-- index.js
|-- simpleTypes.js
|-- userType.js
index.js:
export { UserType, UserTypeBase } from './test';
export { TextMax9Type } from './simpleTypes';
simpleTypes.js:
import t from 'tcomb-form-native';
export const TextMax9Type = t.refinement(t.String, s => s.length <= 9);
test.js:
import t from 'tcomb-form-native';
// import { TextMax9Type } from './'; // <----- NOT WORKING!
import { TextMax9Type } from './simpleTypes'; // <----- OK no error
export const UserTypeBase = {
Name: TextMax9Type,
Surname: TextMax9Type,
};
export const UserType = t.struct(UserTypeBase);
NOT WORKING error:
Invalid argument props {} supplied to struct(props, [name]) combinator (expected a dictionary String-> Type)
So which is the problem with re-export that is exporting an empty object?

Categories

Resources