We are currently using a file shadowing mechanism similar to the gatsby file shadowing: https://www.gatsbyjs.com/docs/conceptual/how-shadowing-works/ With this solution we were able to have one next.js repository and build for different clients and their customizations.
When we are trying to update to webpack 5 we experienced 2 problems:
- File watching of the shadowed files is not given anymore for some reasons
- It seems that it´s mandatory to provide a serializer to properly cache the results
[webpack.cache.PackFileCacheStrategy] Skipped not serializable cache item 'Compilation/modules|/Users/michaelkuczera/Projects/lyska/core-shop/node_modules/next/dist/build/babel/loader/index.js??ruleSet[1].rules[2].use!/Users/michaelkuczera/Projects/lyska/core-shop/src/core/config/config.helper.ts': No serializer registered for WebpackFileShadowPlugin
I tried different things to fix this issue, but couldn´t get it to work and i don´t know if the 2 problems are related and i´m actually stuck.
I tried to make a custom loader because it seems more accurate. It works fine on the first look, but with the following file structure given it came to an end:
- src
- core
- component
- client1
- component
- helper
In this case the overwritten component of the client is requesting a file that is only available in his own client folder. The loader can´t actually resolve the import of ./helper
The following loader is used:
const path = require('path')
const fs = require('fs')
const loaderUtils = require('loader-utils')
module.exports = function(source) {
const callback = this.async()
const { layers } = loaderUtils.getOptions(this)
;(async () => {
const [projectRoot, component] = this.resourcePath.split(
path.join('src', 'core')
)
let overwritable
let usedLayer
layers.forEach(layer => {
if (!component) {
return
}
// construct layer path
const possibleLayerPath = `${path.join(
projectRoot,
'src',
layer,
component
)}`
// check if file exists in customizations
const exists = fs.existsSync(possibleLayerPath)
if (exists) {
overwritable = possibleLayerPath
usedLayer = layer
}
})
if (overwritable) {
// output customization and add path to watchable files
const data = fs.readFileSync(overwritable, 'utf8')
this.addDependency(overwritable)
return data
}
return source
})().then(
res => callback(undefined, res),
err => callback(err)
)
}
Has anybody an idea to resolve the issue either with the Plugin or the Loader?
I would appreciate any help
Plugin source Code:
const fs = require('fs')
const path = require('path')
const checkPossibleComponentPath = require('./checkPossiblePath')
const pathWithoutExtension = fullPath => {
const parsed = path.parse(fullPath)
return path.join(parsed.dir, parsed.name)
}
module.exports = class WebpackFileShadowPlugin {
constructor(layers) {
this.layers = [...layers, 'core']
}
apply(resolver) {
const describedRelative = resolver.getHook('describedRelative')
resolver
.getHook('resolve')
.tapAsync(
'WebpackFileShadowPlugin',
(request, resolveContext, callback) => {
const requestedLayer = this.getMatchingLayerForPath(request.path)
// requested file is not in a layer, and not shadowable
if (!requestedLayer) {
return callback()
}
// get the location of the component relative to `src/${layer}`
const [projectRoot, component] = request.path.split(
path.join('src', requestedLayer)
)
// If a shadowing file requests its original file, then let the request go through
if (
request.context.issuer &&
this.requestPathIsIssuerShadowPath({
component,
requestPath: request.path,
issuerPath: request.context.issuer,
})
) {
return resolver.doResolve(
describedRelative,
request,
null,
{},
callback
)
}
// Shadowing algorithm
const componentPath = this.resolveComponentPath({
projectRoot,
component,
})
if (componentPath) {
return resolver.doResolve(
describedRelative,
{ ...request, path: componentPath || request.path },
null,
{},
callback
)
} else {
return callback()
}
}
)
}
getMatchingLayerForPath(filepath) {
// find out which layer we're requiring from
return this.layers.find(layer => filepath.includes(path.join('src', layer)))
}
requestPathIsIssuerShadowPath({ component, issuerPath }) {
const issuerLayer = this.getMatchingLayerForPath(issuerPath)
// Issuer is not a shadowable file
if (!issuerLayer) return false
const [, issuerComponent] = pathWithoutExtension(issuerPath).split(
path.join('src', issuerLayer)
)
const sameModule =
component === issuerComponent ||
path.join(component, 'index') === issuerComponent
return sameModule
}
resolveComponentPath({ projectRoot, component }) {
// check configured layers for a shadowing file
return this.layers
.map(layer => path.join(projectRoot, 'src', layer, component))
.find(checkPossibleComponentPath)
}
}