Piyush Dankhra

moon indicating dark mode
sun indicating light mode

Last updated: October 02, 2022

Override the component in the PWA Studio project

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 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 the 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 the three files

  1. local-intercept.js to declare the custom interceptor for the project
  2. moduleOverrideWebpackPlugin.js map the components
  3. componentOverrideMapping.js 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"
}
}

Edit the file <pwa-extension>/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;

2. moduleOverrideWebpackPlugin.js

Create the file at <pwa-extension>/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;
});
});
}
};

3. componentOverrideMapping.js

Create the file at <pwa-extension>/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';
module.exports = componentOverrideMapping = {
[`${veniaUiPackagePath}/lib/components/Header/header.js`]: 'src/lib/components/header.js',
// add comma-separated files
};

After copying the file @magento/venia-ui/lib/components/Header/header.js to <pwa-extension>/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';
/*...*/

And the component file is yours to customize and rebuild your PWA studio project.

Cheers!


I'm Piyush Dankhra
Adobe Certified Expert - Adobe Commerce Developer / Front-End Developer / Business Practitioner
Adobe Subject Matter Expert - Adobe Commerce Front End Developer
Working on Magento 2 / PWA Studio