In the previous blog, we checked how to override the component in the PWA Studio custom extension.
In Magento 2, we have the Luma theme as a child of the Blank theme.
To customize the theme, we always create a child theme to override or extend the:
- Templates
- Layouts
- Static assets
So, in PWA Studio:
- How to create a child theme in PWA Studio like Magento 2?
- How to override and extend the default talons, GraphQL, CSS or components?
To do that, we need to override the component using local-intercept.js since PWA Studio does not have the parent-child theme strategy.
To prepare the base, we need to create three files:
local-intercept.jsto declare the custom interceptor for the projectmoduleOverrideWebpackPlugin.jsto map the componentscomponentOverrideMapping.jsto list the components
1. local-intercept.js
Make sure we have this entry in the package.json file:
{
"engines": {
"node": ">=14.x",
"yarn": ">=1.12.0"
},
"pwa-studio": {
"targets": {
"intercept": "./local-intercept.js"
}
}
}package.json
Edit the file <pwa-project>/local-intercept.js and add:
const moduleOverridePlugin = require('./src/plugins/moduleOverrideWebpackPlugin');
const componentOverrideMapping = require('./componentOverrideMapping');
function localIntercept(targets) {
targets.of('@magento/pwa-buildpack').webpackCompiler.tap(compiler => {
new moduleOverridePlugin(componentOverrideMapping).apply(compiler);
});
}
module.exports = localIntercept;local-intercept.js
2. moduleOverrideWebpackPlugin.js
Create the file at <pwa-project>/src/plugins/moduleOverrideWebpackPlugin.js:
const path = require('path');
const glob = require('glob');
module.exports = class NormalModuleOverridePlugin {
constructor(moduleOverrideMap) {
this.name = 'NormalModuleOverridePlugin';
this.moduleOverrideMap = moduleOverrideMap;
}
requireResolveIfCan(id, options = undefined) {
try {
return require.resolve(id, options);
} catch (e) {
return undefined;
}
}
resolveModulePath(context, request) {
const filePathWithoutExtension = path.resolve(context, request);
const files = glob.sync(`${filePathWithoutExtension}@(|.*)`);
if (files.length === 0) {
throw new Error(`There is no file '${filePathWithoutExtension}'`);
}
if (files.length > 1) {
throw new Error(`There is more than one file '${filePathWithoutExtension}'`);
}
return require.resolve(files[0]);
}
resolveModuleOverrideMap(context, map) {
return Object.keys(map).reduce(
(result, x) => ({
...result,
[require.resolve(x)]:
this.requireResolveIfCan(map[x]) || this.resolveModulePath(context, map[x])
}),
{}
);
}
apply(compiler) {
if (Object.keys(this.moduleOverrideMap).length === 0) {
return;
}
const moduleMap = this.resolveModuleOverrideMap(compiler.context, this.moduleOverrideMap);
compiler.hooks.normalModuleFactory.tap(this.name, nmf => {
nmf.hooks.beforeResolve.tap(this.name, resolve => {
if (!resolve) {
return;
}
const moduleToReplace = this.requireResolveIfCan(resolve.request, {
paths: [resolve.context]
});
if (moduleToReplace && moduleMap[moduleToReplace]) {
resolve.request = moduleMap[moduleToReplace];
}
return resolve;
});
});
}
};src/plugins/moduleOverrideWebpackPlugin.js
3. componentOverrideMapping.js
Create the file at <pwa-project>/componentOverrideMapping.js to add entries of component files that we want to override.
For example, to override the component @magento/venia-ui/lib/components/Header/header.js:
const veniaUiPackagePath = '@magento/venia-ui';
// Map core Venia components to your local overrides
module.exports = {
[`${veniaUiPackagePath}/lib/components/Header/header.js`]: 'src/lib/components/Header/header.js'
// add comma-separated files
};componentOverrideMapping.js
After copying the file @magento/venia-ui/lib/components/Header/header.js to
<pwa-project>/src/lib/components/Header/header.js, edit the header.js file and replace the import paths with the relevant package, so it points to the correct import path of the core component. For example:
import React, { Fragment, Suspense } from 'react';
import { shape, string } from 'prop-types';
import { Link, Route } from 'react-router-dom';
// import Logo from '../Logo';
import Logo from '@magento/venia-ui/lib/components/Logo';
// import AccountTrigger from './accountTrigger';
import AccountTrigger from '@magento/venia-ui/lib/components/Header/accountTrigger';
// import CartTrigger from './cartTrigger';
import CartTrigger from '@magento/venia-ui/lib/components/Header/cartTrigger';
// import NavTrigger from './navTrigger';
import NavTrigger from '@magento/venia-ui/lib/components/Header/navTrigger';
// import SearchTrigger from './searchTrigger';
import SearchTrigger from '@magento/venia-ui/lib/components/Header/searchTrigger';
// import OnlineIndicator from './onlineIndicator';
import OnlineIndicator from '@magento/venia-ui/lib/components/Header/onlineIndicator';
import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
// import { useStyle } from '../../classify';
import { useStyle } from '@magento/venia-ui/lib/classify';
// import defaultClasses from './header.module.css';
import defaultClasses from '@magento/venia-ui/lib/components/Header/header.module.css';
// import StoreSwitcher from './storeSwitcher';
import StoreSwitcher from '@magento/venia-ui/lib/components/Header/storeSwitcher';
// import CurrencySwitcher from './currencySwitcher';
import CurrencySwitcher from '@magento/venia-ui/lib/components/Header/currencySwitcher';
// import MegaMenu from '../MegaMenu';
import MegaMenu from '@magento/venia-ui/lib/components/MegaMenu';
// import PageLoadingIndicator from '../PageLoadingIndicator';
import PageLoadingIndicator from '@magento/venia-ui/lib/components/PageLoadingIndicator';
import { useIntl } from 'react-intl';
/* ...rest of the component code... */src/lib/components/Header/header.js
Once this override is in place, the component file is yours to customize. Rebuild your PWA Studio project and your changes will be picked up instead of the core Venia implementation.
Cheers!