Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/pwa-kit-dev/src/configs/webpack/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,21 @@ import {sdkReplacementPlugin} from './plugins'
import {CLIENT, SERVER, CLIENT_OPTIONAL, SSR, REQUEST_PROCESSOR} from './config-names'

// Utilities
import {ruleForApplicationExtensibility} from '@salesforce/pwa-kit-extension-sdk/configs/webpack'
import {ruleForApplicationExtensibility, ruleForOverrideResolver} from '@salesforce/pwa-kit-extension-sdk/configs/webpack'
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
import {
buildAliases,
nameRegex,
getConfiguredExtensions
} from '@salesforce/pwa-kit-extension-sdk/shared/utils'
import { symlink } from 'fs'

const projectDir = process.cwd()
const pkg = fse.readJsonSync(resolve(projectDir, 'package.json'))
const buildDir = process.env.PWA_KIT_BUILD_DIR
? resolve(process.env.PWA_KIT_BUILD_DIR)
: resolve(projectDir, 'build')

const isMonoRepo = fse.existsSync(resolve(projectDir, '..', '..', 'lerna.json'))
const production = 'production'
const development = 'development'
const analyzeBundle = process.env.MOBIFY_ANALYZE === 'true'
Expand Down Expand Up @@ -270,7 +271,8 @@ const baseConfig = (target) => {
configured: getConfiguredExtensions(getConfig()),
target: 'node'
}
})
}),
ruleForOverrideResolver({target, projectDir, isMonoRepo})
].filter(Boolean)
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/pwa-kit-extension-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
## v4.0.0-extensibility-preview.2 (Dec 09, 2024)
- Add support for `.force_overrides` project file. (#2207)[https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2207]
- Initial release of Extensibility SDK. (#2099)[https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2099]
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,33 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {LoaderContext} from 'webpack'
import fse from 'fs-extra'
import os from 'os'
import path from 'path'
import resolve from 'resolve'

// Local Imports
import {buildCandidatePaths, getPackageName} from '../../shared/utils'
import {buildCandidatePaths, getPackageName, SETUP_FILE_REGEX} from '../../shared/utils'

// Types
import type {ExtendedCompiler} from './types'

// Constants
const EXTENSION_PACKAGE_PREFIX = 'extension-'
const EXTENSION_PACKAGE_WORKSPACE = '@salesforce'
const IMPORT_REGEX = /import\s+(?:(?:[\w*\s{},]*)\s+from\s+)?['"](\..*?)['"]/g
const OVERRIDABLE_FILE_NAME = '.force_overrides'
const MONO_REPO_WORKSPACE_FOLDER = `${path.sep}packages${path.sep}`
const NODE_MODULES_FOLDER = `${path.sep}node_modules${path.sep}`
const REQUIRES_REGEX = /require\(['"](\..*?)['"]\)/g
const SRC = 'src'

// Cache for processed overrides
const OVERRIDABLE_CACHE = {
node: [] as string[],
web: [] as string[]
}

/**
* Webpack loader to override the resolution of a module based on the PWA-Kit applications
* extension configuration.
Expand Down Expand Up @@ -54,8 +66,8 @@ const OverrideResolverLoader = function (this: LoaderContext<any>) {
}

// Lets use the compiler configuration to ensure we are resolving the correct file extensions.
const compilerOptions = this._compiler!.options
const extensions = compilerOptions.resolve?.extensions || []
const compilerOptions = this._compiler?.options
const extensions = compilerOptions?.resolve?.extensions || []
const basedir = options?.baseDir || process.cwd()
const applicationExtensions = compiler?.custom?.extensions || []

Expand Down Expand Up @@ -114,5 +126,87 @@ const OverrideResolverLoader = function (this: LoaderContext<any>) {
})
}

/**
*
* @param {*} source
* @returns
*/
const validateOverrideSource = (source: string, options: any = {}) => {
const {isMonoRepo = false, target = 'node', overridables = []} = options
const isExtensionFile = source.includes(`${path.sep}${EXTENSION_PACKAGE_PREFIX}`)
const isSetupFile = SETUP_FILE_REGEX.test(source)
const targetCache = OVERRIDABLE_CACHE[target as keyof typeof OVERRIDABLE_CACHE]

// Exit early if we have:
// 1. Processed this file already.
// 2. The file is not an extension file.
// 3. The file is an extension setup file.
if (targetCache.includes(source) || !isExtensionFile || isSetupFile) {
return false
}

// Because our webpack configuration is setup to resolve symlinks, we need to normalize the source path because
// the source path passed to the loaded is not representative of what you would see in a generated project (e.g.
// it doesn't resolve to being in the node_modules folder).
// NOTE:
// For now we are going to make the assumption that all our extension projects in our mono repo
// are part of the `@salesforce` namespace, this is pretty safe.
const normalizedSource = isMonoRepo
? `${EXTENSION_PACKAGE_WORKSPACE}${path.sep}${
source.split(MONO_REPO_WORKSPACE_FOLDER).pop() ?? ''
}`
: source.split(NODE_MODULES_FOLDER).pop()

// Check if the normalized source is in the list of overridables.
const hasOverride = overridables.includes(normalizedSource)

// If we have an override, add it to the cache so we don't process it again.
if (hasOverride) {
targetCache.push(source)
}

return hasOverride
}

/**
* Generates a Webpack rule for application extensibility, configuring the loader for
* handling application extensions based on the target (e.g., 'node' for server-side,
* 'react' for client-side).
*
* @param {Object} [options={}] - Options to customize the Webpack rule.
* @param {Object} [options.projectDir={}] - Loader-specific options.
* @param {string} [options.target=DEFAULT_TARGET] - The target environment, either 'node' or 'react'.
* @param {Object} [options.isMonoRepo] - Optional application configuration to pass to the loader.
*
* @returns {Object} A Webpack rule configuration object for handling application extensions.
*/
export const ruleForOverrideResolver = (options: any = {}) => {
const {projectDir, target, isMonoRepo} = options
let overridables: string[] = []

try {
overridables = fse
.readFileSync(path.join(projectDir, OVERRIDABLE_FILE_NAME), 'utf8')
.split(/\r?\n/)
.filter((line) => !line.startsWith('//'))
} catch (e) {
// If where is no .force_overrides file, we can safely return null.
return null
}

return {
test: (source: string) => {
return validateOverrideSource(source, {
isMonoRepo,
target,
overridables
})
},
use: {
loader: '@salesforce/pwa-kit-extension-sdk/configs/webpack/overrides-resolver-loader'
}
}
}

// Export the loader as the default export with proper typing
export default OverrideResolverLoader
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ import {
import {expand} from './index'

// CONSTANTS
// const REACT_EXTENSIBILITY_FILE = 'setup-app'
// const EXPRESS_EXTENSIBILITY_FILE = 'setup-server'
// const SUPPORTED_FILE_TYPES = ['.ts', '.js']
export const REACT_SETUP_FILE = 'setup-app'
export const EXPRESS_SETUP_FILE = 'setup-server'
export const SUPPORTED_FILE_TYPES = ['ts', 'js', 'tsx', 'jsx']
export const SETUP_FILE_REGEX = new RegExp(
`(${REACT_SETUP_FILE}|${EXPRESS_SETUP_FILE}).(${SUPPORTED_FILE_TYPES.join('|')})$`,
'i'
)

// TODO: Update this block comment.
/**
Expand Down
11 changes: 11 additions & 0 deletions packages/template-typescript-minimal/.force_overrides
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// DISCLAIMER
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we missing adding documentation in README.md files or similar to let users know how to use this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've create this small ticket to do this later.

https://gus.lightning.force.com/lightning/_classic/%2Fa07EE0000285F7zYAE

//
// BY USING THIS FILE, YOU AGREE THAT THE FUNCTIONALITY OF YOUR INSTALLED EXTENSION(S) IS NOT GUARANTEED.
// ADDITIONALLY UPGRADABILITY OF EXTENSIONS CAN ALSO NO LONGER BE GUARANTEED AND IS NOT SUPPORTED BY SALESFORCE.
// USE ONLY AS A TEMPORARY SOLUTION TO URGENTLY PATCH/UPDATE AN EXTENSION.
//
// USAGE:
// PLACE THE RELATIVE __POSIX__ PATH TO THE EXTENSION FILE YOU WANT TO OVERRIDE STARTING WITH THE EXTENSION PACKAGE NAME.
// MULTIPLE OVERRIDES CAN BE ADDED TO THIS FILE, ONE PER LINE.\
// EXAMPLE:
// @salesforce/extension-sample/src/pages/home.tsx
Copy link
Contributor

@kevinxh kevinxh Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 are the file paths supposed to be relative paths or absolute paths? The example look like neither

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's really more of a module import path, I was wavering on this a little too. I could make it something like:

./@salesforce/extension-sample/src/pages/home.tsx

or

./node_modules/@salesforce/extension-sample/src/pages/home.tsx

I think the second one is probably the way to go and shouldn't be hard to fix.

Loading