363 lines
8.3 KiB
Markdown
363 lines
8.3 KiB
Markdown
---
|
|
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/shared-packages.mdc
|
|
confidence: 0.9
|
|
namespace: work
|
|
last_synced: 2026-04-28
|
|
alwaysApply: false
|
|
---
|
|
|
|
# Shared Packages Development Guidelines
|
|
|
|
## Package Structure
|
|
|
|
### Standard Package Layout
|
|
```
|
|
SharedPackages/
|
|
├── FeatureName/
|
|
│ ├── Package.swift
|
|
│ ├── README.md
|
|
│ ├── Sources/
|
|
│ │ └── FeatureName/
|
|
│ │ ├── Public/ # Public API
|
|
│ │ ├── Internal/ # Internal implementation
|
|
│ │ └── Resources/ # Assets and resources
|
|
│ └── Tests/
|
|
│ └── FeatureNameTests/
|
|
│ └── FeatureTests.swift
|
|
```
|
|
|
|
### Package.swift Configuration
|
|
```swift
|
|
// swift-tools-version: 5.7
|
|
import PackageDescription
|
|
|
|
let package = Package(
|
|
name: "FeatureName",
|
|
platforms: [
|
|
.iOS(.v15),
|
|
.macOS(.v12)
|
|
],
|
|
products: [
|
|
.library(
|
|
name: "FeatureName",
|
|
targets: ["FeatureName"]
|
|
)
|
|
],
|
|
dependencies: [
|
|
// Only include truly necessary dependencies
|
|
.package(url: "https://github.com/DuckDuckGo/BrowserServicesKit", from: "1.0.0")
|
|
],
|
|
targets: [
|
|
.target(
|
|
name: "FeatureName",
|
|
dependencies: ["BrowserServicesKit"],
|
|
resources: [
|
|
.process("Resources")
|
|
]
|
|
),
|
|
.testTarget(
|
|
name: "FeatureNameTests",
|
|
dependencies: ["FeatureName"]
|
|
)
|
|
]
|
|
)
|
|
```
|
|
|
|
## Cross-Platform Compatibility
|
|
|
|
### Platform-Specific Code
|
|
```swift
|
|
#if os(iOS)
|
|
import UIKit
|
|
public typealias PlatformView = UIView
|
|
public typealias PlatformViewController = UIViewController
|
|
public typealias PlatformColor = UIColor
|
|
#elseif os(macOS)
|
|
import AppKit
|
|
public typealias PlatformView = NSView
|
|
public typealias PlatformViewController = NSViewController
|
|
public typealias PlatformColor = NSColor
|
|
#endif
|
|
|
|
// Use platform-agnostic types
|
|
public protocol CrossPlatformViewProtocol {
|
|
var backgroundColor: PlatformColor? { get set }
|
|
}
|
|
```
|
|
|
|
### Conditional Compilation
|
|
```swift
|
|
public class FeatureManager {
|
|
public func performAction() {
|
|
#if os(iOS)
|
|
performIOSAction()
|
|
#elseif os(macOS)
|
|
performMacOSAction()
|
|
#endif
|
|
}
|
|
|
|
#if os(iOS)
|
|
private func performIOSAction() {
|
|
// iOS-specific implementation
|
|
}
|
|
#endif
|
|
|
|
#if os(macOS)
|
|
private func performMacOSAction() {
|
|
// macOS-specific implementation
|
|
}
|
|
#endif
|
|
}
|
|
```
|
|
|
|
## API Design
|
|
|
|
### Public API Guidelines
|
|
```swift
|
|
// Mark public APIs clearly
|
|
public protocol FeatureServiceProtocol {
|
|
func fetchData() async throws -> [Item]
|
|
}
|
|
|
|
public final class FeatureService: FeatureServiceProtocol {
|
|
// Use dependency injection
|
|
private let networkClient: NetworkClientProtocol
|
|
|
|
public init(networkClient: NetworkClientProtocol) {
|
|
self.networkClient = networkClient
|
|
}
|
|
|
|
public func fetchData() async throws -> [Item] {
|
|
// Implementation
|
|
}
|
|
}
|
|
```
|
|
|
|
### Internal Implementation
|
|
```swift
|
|
// Keep implementation details internal
|
|
internal final class FeatureImplementation {
|
|
// Not exposed to package consumers
|
|
}
|
|
|
|
// Use extensions for internal helpers
|
|
internal extension String {
|
|
var sanitized: String {
|
|
// Internal helper method
|
|
}
|
|
}
|
|
```
|
|
|
|
## Resource Management
|
|
|
|
### Bundled Resources
|
|
```swift
|
|
public enum FeatureResources {
|
|
private static let bundle = Bundle.module
|
|
|
|
public static var configuration: Data {
|
|
guard let url = bundle.url(forResource: "config", withExtension: "json"),
|
|
let data = try? Data(contentsOf: url) else {
|
|
fatalError("Missing required resource: config.json")
|
|
}
|
|
return data
|
|
}
|
|
|
|
public static func image(named name: String) -> PlatformImage? {
|
|
#if os(iOS)
|
|
return UIImage(named: name, in: bundle, with: nil)
|
|
#elseif os(macOS)
|
|
return bundle.image(forResource: name)
|
|
#endif
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dependency Management
|
|
|
|
### Minimal Dependencies
|
|
```swift
|
|
// Avoid unnecessary dependencies
|
|
// Bad: Importing entire framework for one function
|
|
import HeavyFramework
|
|
|
|
// Good: Implement minimal version or use protocol
|
|
protocol DateFormatterProtocol {
|
|
func string(from date: Date) -> String
|
|
}
|
|
```
|
|
|
|
### Version Management
|
|
```swift
|
|
// Use semantic versioning
|
|
// Package.swift
|
|
dependencies: [
|
|
.package(url: "https://github.com/example/package",
|
|
from: "1.0.0"), // Allows 1.x.x
|
|
.package(url: "https://github.com/example/strict",
|
|
exact: "2.1.0"), // Exact version
|
|
.package(url: "https://github.com/example/range",
|
|
"1.0.0"..<"2.0.0") // Version range
|
|
]
|
|
```
|
|
|
|
## Testing Shared Packages
|
|
|
|
### Cross-Platform Tests
|
|
```swift
|
|
import XCTest
|
|
@testable import FeatureName
|
|
|
|
final class FeatureTests: XCTestCase {
|
|
func testCrossPlatformBehavior() {
|
|
let feature = Feature()
|
|
|
|
#if os(iOS)
|
|
XCTAssertNotNil(feature.iosSpecificProperty)
|
|
#elseif os(macOS)
|
|
XCTAssertNotNil(feature.macOSSpecificProperty)
|
|
#endif
|
|
|
|
// Test common behavior
|
|
XCTAssertEqual(feature.commonProperty, expectedValue)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Test Utilities
|
|
```swift
|
|
// Provide test utilities in a separate target
|
|
public extension XCTestCase {
|
|
func waitForCondition(
|
|
_ condition: @autoclosure () -> Bool,
|
|
timeout: TimeInterval = 1.0,
|
|
message: String = "Condition not met"
|
|
) {
|
|
let expectation = expectation(description: message)
|
|
|
|
Task {
|
|
while !condition() {
|
|
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1s
|
|
}
|
|
expectation.fulfill()
|
|
}
|
|
|
|
wait(for: [expectation], timeout: timeout)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Documentation
|
|
|
|
### Package Documentation
|
|
```swift
|
|
/// A service for managing user preferences across platforms.
|
|
///
|
|
/// This service provides a unified interface for storing and retrieving
|
|
/// user preferences, with platform-specific implementations for iOS and macOS.
|
|
///
|
|
/// ## Usage Example
|
|
/// ```swift
|
|
/// let preferences = UserPreferencesService()
|
|
/// preferences.set("value", for: .theme)
|
|
/// let theme = preferences.get(.theme)
|
|
/// ```
|
|
public final class UserPreferencesService {
|
|
/// Initializes a new preferences service.
|
|
///
|
|
/// - Parameter storage: The storage backend to use. Defaults to UserDefaults.
|
|
public init(storage: PreferencesStorage = .userDefaults) {
|
|
// Implementation
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration and Versioning
|
|
|
|
### API Evolution
|
|
```swift
|
|
public protocol FeatureProtocolV2 {
|
|
// New required method
|
|
func newRequiredMethod()
|
|
|
|
// Existing method
|
|
func existingMethod()
|
|
}
|
|
|
|
// Provide default implementation for backward compatibility
|
|
public extension FeatureProtocolV2 {
|
|
func newRequiredMethod() {
|
|
// Default implementation
|
|
}
|
|
}
|
|
|
|
// Deprecation
|
|
@available(*, deprecated, renamed: "newMethod()")
|
|
public func oldMethod() {
|
|
newMethod()
|
|
}
|
|
```
|
|
|
|
### Breaking Changes
|
|
```swift
|
|
// Use versioned types when making breaking changes
|
|
public struct ConfigurationV1 {
|
|
public let setting: String
|
|
}
|
|
|
|
public struct ConfigurationV2 {
|
|
public let setting: String
|
|
public let newRequired: Bool
|
|
|
|
// Provide migration
|
|
public init(from v1: ConfigurationV1) {
|
|
self.setting = v1.setting
|
|
self.newRequired = false // Default value
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Lazy Loading
|
|
```swift
|
|
public final class ResourceManager {
|
|
// Lazy load expensive resources
|
|
private lazy var heavyResource: HeavyResource = {
|
|
return HeavyResource()
|
|
}()
|
|
|
|
// Use computed properties for lightweight calculations
|
|
public var lightweightValue: String {
|
|
return "Calculated on demand"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Memory Management
|
|
```swift
|
|
public final class CacheManager {
|
|
private let cache = NSCache<NSString, CacheItem>()
|
|
|
|
public init() {
|
|
// Configure cache limits
|
|
cache.countLimit = 100
|
|
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
|
|
|
|
// Respond to memory warnings
|
|
#if os(iOS)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(clearCache),
|
|
name: UIApplication.didReceiveMemoryWarningNotification,
|
|
object: nil
|
|
)
|
|
#endif
|
|
}
|
|
|
|
@objc private func clearCache() {
|
|
cache.removeAllObjects()
|
|
}
|
|
}
|
|
```
|