Reputation: 637
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.
Upvotes: 1
Views: 222
Reputation: 3536
According to the comments I can suggest these workarounds using custom loader:
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;
}
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
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.
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.
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.
Upvotes: 2