Skip to content

Commit

Permalink
feat(core): chromium fallback installer if system chrome is missing (#97
Browse files Browse the repository at this point in the history
)
  • Loading branch information
harlan-zw authored May 13, 2023
1 parent c0dee7e commit b261c58
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 43 deletions.
7 changes: 2 additions & 5 deletions docs/content/1.guide/1.getting-started/0.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ npx unlighthouse --site <your-site>
# OR pnpm dlx unlighthouse --site <your-site>
```

If you don't have an accessible chrome installation, you can use the `unlighthouse-puppeteer` binary.

```bash
npx unlighthouse-puppeteer --site example.com
```
By default Unlighthouse will attempt to use your system Chrome / Chromium install.
If you these are missing, a Chromium binary will be installed on your system.

To learn more about the CLI and the arguments, head over to [CLI Integration](/integrations/cli).

Expand Down
34 changes: 34 additions & 0 deletions docs/content/1.guide/guides/chrome-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Chrome Dependency

Unlighthouse aims to keep the installation size small, for this reason it depends natively on your locally installed
Chrome.

As a fallback, it will download a Chromium binary for you.

## Disabling system chrome

You can disable the system chrome usage by providing `chrome.useSystem: false`. This will force the fallback installer to run.

## Customizing the fallback installer

When Chrome can't be found on your system or if the `chrome.useSystem: false` flag is passed, then a fallback will be attempted.

This fallback will download a chrome binary for your system and use that path.

There are a number of options you can customize on this.

- `chrome.useDownloadFallback` - Disables the fallback installer
- `chrome.downloadFallbackVersion` - Which version of chromium to use (default `1095492`)
- `chrome.downloadFallbackCacheDir` - Where the binary should be saved (default `$home/.unlighthouse`)

## Using your own chrome path

You can provide your own chrome path by setting `puppeteerOptions.executablePath`.

```ts
export default {
puppeteerOptions: {
executablePath: '/usr/bin/chrome'
}
}
```
14 changes: 0 additions & 14 deletions docs/content/2.integrations/0.cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,6 @@ npx unlighthouse --site <your-site>
# OR pnpm dlx unlighthouse --site <your-site>
```

### Chromium Dependency

Unlighthouse aims to keep the installation size small, for this reason it depends natively on your locally installed
chrome.

If you get errors about puppeteer or chrome not being available, the easiest way to resolve it is
with the `unlighthouse-puppeteer` binary.

```bash
unlighthouse-puppeteer --site example.com
```

You will need to use `unlighthouse-puppeteer` anywhere it says `unlighthouse`.

## Usage

Once installed globally you'll have access to Unlighthouse through the `unlighthouse` binary.
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
}
},
"dependencies": {
"@puppeteer/browsers": "^1.2.0",
"@types/wrap-ansi": "^8.0.1",
"@unlighthouse/client": "workspace:../client",
"@unrouted/core": "0.5.0",
Expand Down
72 changes: 56 additions & 16 deletions packages/core/src/resolveConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { join } from 'node:path'
import { homedir } from 'node:os'
import { existsSync } from 'node:fs'
import { Buffer } from 'node:buffer'
import { createDefu, defu } from 'defu'
import { pick } from 'lodash-es'
import { pathExists } from 'fs-extra'
import { computeExecutablePath, install } from '@puppeteer/browsers'
import type { InstallOptions } from '@puppeteer/browsers'
import { Launcher } from 'chrome-launcher'
import puppeteer from 'puppeteer-core'
import { resolve } from 'mlly'
Expand Down Expand Up @@ -60,6 +64,13 @@ export const resolveUserConfig: (userConfig: UserConfig) => Promise<ResolvedUser
}
}

config.chrome = defu(config.chrome || {}, {
useSystem: true,
useDownloadFallback: true,
downloadFallbackVersion: 1095492,
downloadFallbackCacheDir: join(homedir(), '.unlighthouse'),
})

if (config.auth) {
config.lighthouseOptions.extraHeaders = config.lighthouseOptions.extraHeaders || {}
if (!config.lighthouseOptions.extraHeaders.Authorization) {
Expand Down Expand Up @@ -108,6 +119,7 @@ export const resolveUserConfig: (userConfig: UserConfig) => Promise<ResolvedUser
config.routerPrefix = withSlashes(config.routerPrefix)

config.puppeteerOptions = config.puppeteerOptions || {}
config.puppeteerClusterOptions = config.puppeteerClusterOptions || {}
// @ts-expect-error untyped
config.puppeteerOptions = defu({
// set viewport
Expand All @@ -119,30 +131,58 @@ export const resolveUserConfig: (userConfig: UserConfig) => Promise<ResolvedUser
ignoreHTTPSErrors: true,
}, config.puppeteerOptions)

let foundChrome = !!config.puppeteerOptions?.executablePath
// if user is using the default chrome binary options
if (!config.puppeteerOptions?.executablePath && !config.puppeteerClusterOptions?.puppeteer) {
if (config.chrome.useSystem && !foundChrome) {
// we'll try and resolve their local chrome
const chromePath = Launcher.getFirstInstallation()
if (chromePath) {
logger.debug(`Found chrome at \`${chromePath}\`.`)
logger.info(`Using system chrome located at: \`${chromePath}\`.`)
// set default to puppeteer core
config.puppeteerClusterOptions = defu({ puppeteer }, config.puppeteerClusterOptions || {})
config.puppeteerClusterOptions.puppeteer = puppeteer
// point to our pre-installed chrome version
config.puppeteerOptions!.executablePath = Launcher.getFirstInstallation()
config.puppeteerOptions!.executablePath = chromePath
foundChrome = true
}
}
if (!foundChrome) {
// if we can't find their local chrome, we just need to make sure they have puppeteer, this is a similar check
// puppeteer-cluster will do, but we can provide a nicer error
try {
await resolve('puppeteer')
foundChrome = true
logger.info('Using puppeteer dependency for chrome.')
}
else {
// if we can't find their local chrome, we just need to make sure they have puppeteer, this is a similar check
// puppeteer-cluster will do, but we can provide a nicer error
try {
await resolve('puppeteer')
}
catch (e) {
logger.fatal('Failed to find a chrome / chromium binary to run. Add the puppeteer dependency to your project to resolve.', e)
logger.info('Run the following: \`npm install -g puppeteer\`')
process.exit(0)
}
catch (e) {
logger.debug('Puppeteer does not exist as a dependency.', e)
}
}

if (config.chrome.useDownloadFallback && !foundChrome) {
const browserOptions = {
cacheDir: join(homedir(), '.unlighthouse'),
buildId: '1095492',
browser: 'chromium',
} as InstallOptions
const chromePath = computeExecutablePath(browserOptions)
if (!existsSync(chromePath)) {
logger.warn('Failed to find chromium, attempting to download it instead.')
let lastPercent = 0
await install({
...browserOptions,
downloadProgressCallback: (downloadedBytes, toDownloadBytes) => {
const percent = Math.round(downloadedBytes / toDownloadBytes * 100)
if (percent % 5 === 0 && lastPercent !== percent) {
logger.info(`Downloading chromium: ${percent}%`)
lastPercent = percent
}
},
})
}
logger.info(`Using temporary downloaded chromium v1095492 located at: ${chromePath}`)
config.puppeteerOptions!.executablePath = chromePath
foundChrome = true
}
if (!foundChrome)
throw new Error('Failed to find chrome. Please ensure you have a valid chrome installed.')
return config as ResolvedUserConfig
}
27 changes: 27 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,33 @@ export interface ResolvedUserConfig {
sameDomainDelay: number
puppeteer: any
}>

chrome: {
/**
* Should chrome be attempted to be used from the system.
*
* @default true
*/
useSystem: boolean
/**
* If no chrome can be found in the system, should a download fallback be attempted.
*
* @default true
*/
useDownloadFallback: boolean
/**
* When downloading the fallback which version of chrome should be used.
*
* @default 1095492
*/
downloadFallbackVersion: string | number
/**
* The directory to install the downloaded fallback browser.
*
* @default $home/.unlighthouse
*/
downloadFallbackCacheDir: string
}
}

export type DeepPartial<T> = T extends Function ? T : (T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T)
Expand Down
2 changes: 2 additions & 0 deletions packages/unlighthouse-puppeteer/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# unlighthouse-puppeteer

Note: This package is now deprecated. The Unlighthouse CLI will install any browser dependencies needed.

Alias package for [Unlighthouse](https://github.com/harlan-zw/unlighthouse) with puppeteer as a dependency.

## License
Expand Down
18 changes: 10 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b261c58

Please sign in to comment.