@speedy-js/depcost
Version:
[](https://npm.im/@speedy-js/depcost)
213 lines (189 loc) • 4.45 kB
JavaScript
/**
* Module dependencies
*/
const { join, isAbsolute, resolve } = require('path')
const hash = require('hash-sum')
const bytes = require('bytes')
const { removeSync, writeFile, existsSync } = require('fs-extra')
const chalk = require('chalk')
const execa = require('execa')
const log = require('npmlog')
const parsePackageName = require('./parse-package-name')
const store = require('./store')
const { getTmpDirByTS } = require('./tmp')
const exec = (commands, opts) => {
const [command, ...args] = commands.split(' ')
return execa(command, args, opts)
}
/**
* Get require time of a entry.
*
* @param {string} entry
* @param {string} tmpfile
*/
async function getRequireTime(entry, tmpfile) {
if (!tmpfile) {
const tmpdir = getTmpDirByTS()
tmpfile = join(tmpdir, `index-${hash(entry)}.js`)
}
await writeFile(
tmpfile,
`
console.time('__depcost__')
try {
require('${entry}')
} catch(e) {
// Do not need handle it.
}
console.timeEnd('__depcost__')
`,
'utf-8',
)
log.info(entry, 'calculate require time')
try {
// e.g. '__depcost__: 49.518ms'
const { stdout: execOutput } = await exec(COMMANDS.execNode(tmpfile))
const matched = execOutput.match(/__depcost__\:\s(.*ms)/)
if (matched) {
return matched[1].trim()
}
return 'N/A'
} catch (e) {
console.log(e)
return 'N/A'
}
}
/**
* Get size of a directory
*
* @param {string} cwd
*/
async function getDirectorySize(cwd) {
const sizeCommand = COMMANDS.size(cwd)
log.info(cwd, sizeCommand)
const { stdout: sizeOutput } = await exec(sizeCommand, { cwd })
const sizes = sizeOutput.split('\t')
return sizes[0]
}
/**
* Get entry by directory path
*
* @param {string} dir
*/
function getEntryByDir(dir) {
return join(dir, 'index.js')
}
/**
* Parse and get correct install script.
*/
function getInstallScript(npmClient, pkg) {
if (npmClient.endsWith('npm')) {
return `${npmClient} install ${pkg} -D`
}
if (npmClient === 'yarn') {
return `${npmClient} add ${pkg} -D`
}
throw new Error(`Unknown npm client: ${npmClient}`)
}
/**
* Core commands.
*/
const COMMANDS = {
init: 'npm init -y',
installPkg: (pkg, npmClient) => getInstallScript(npmClient, pkg),
size: dir => `du -sk ${dir}`,
execNode: file => `node ${file}`,
}
/**
* A simple util to make "process.on" only called once.
*/
const onProcessExit = (() => {
const listeners = []
process.on('exit', () => {
listeners.forEach(listener => listener())
})
return listener => listeners.push(listener)
})()
/**
* Retrieve cost of a package
*
* @param options
* @return {Promise<{
* pkg: Record<string, unknown>,
* name: string,
* version: string,
* size: string
* rawSize: number,
* requireTime: string,
* }>}
*/
module.exports = async function (options) {
const { pkg, track, npmClient, resolvelocal = false } = options;
const parsed = parsePackageName(pkg);
const cache = store.get(pkg);
let usingLocal = false;
/**
* handle cache.
*/
if (
cache
&& cache.pkg
&& cache.name
&& cache.version
&& cache.size
&& cache.rawSize
&& cache.requireTime
) {
return cache;
}
log.info('getSingleDepCost', pkg)
/**
* Create a temp dir for calculating size.
*/
let tempDir = getTmpDirByTS()
let dir = tempDir;
if (track) {
console.log(chalk.gray('dir'), dir)
} else {
onProcessExit(() => {
log.info('clean temp directory ...')
removeSync(dir)
})
}
/**
* for local directory.
*/
if (resolvelocal && existsSync(pkg)) {
if (isAbsolute(pkg)) {
dir = pkg
} else {
dir = resolve(pkg)
}
usingLocal = true;
console.log(`leverage local directory: ${chalk.gray(dir)}`);
}
log.info(pkg, COMMANDS.init)
await exec(COMMANDS.init, { cwd: dir })
const installPkgCommand = COMMANDS.installPkg(pkg, npmClient)
log.info(pkg, installPkgCommand)
await exec(installPkgCommand, { cwd: dir })
return Promise.all([
getDirectorySize(dir),
getRequireTime(parsed.name, getEntryByDir(dir))
]).then(
([size, requireTime]) => {
const rawSize = Number(size)
const ret = {
pkg,
name: parsed.name,
version: parsed.version,
size: bytes(rawSize * 1024),
rawSize, // unit: kb
requireTime,
}
store.set(pkg, ret)
return ret
},
)
}
module.exports.getRequireTime = getRequireTime