Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental skip npm install #33

Closed
wants to merge 1 commit into from
Closed
Changes from all 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
103 changes: 82 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import rimraf from 'rimraf';
import chalk from 'chalk';
import ora from 'ora';
import yargs from 'yargs-parser';
import https from 'https';
import mkdirp from 'mkdirp';
import os from 'os';

import * as rollup from 'rollup';
import rollupPluginNodeResolve from 'rollup-plugin-node-resolve';
Expand All @@ -18,9 +21,12 @@ export interface InstallOptions {
isStrict?: boolean;
isOptimized?: boolean;
skipFailures?: boolean;
skipNpm?: boolean;
}

const cwd = process.cwd();
const cdnUrl = 'https://unpkg.com';
const cwdManifest = require(path.join(cwd, 'package.json'));
const banner = chalk.bold(`@pika/web`) + ` installing... `;
const detectionResults = [];
let spinner = ora(banner);
Expand All @@ -37,7 +43,9 @@ function showHelp() {
}

function formatDetectionResults(skipFailures): string {
return detectionResults.map(([d, yn]) => yn ? chalk.green(d) : skipFailures ? chalk.dim(d) : chalk.red(d)).join(', ');
return detectionResults
.map(([d, yn]) => (yn ? chalk.green(d) : skipFailures ? chalk.dim(d) : chalk.red(d)))
.join(', ');
}

function logError(msg) {
Expand All @@ -46,13 +54,25 @@ function logError(msg) {
spinner.fail();
}


class ErrorWithHint extends Error {
constructor(message: string, public readonly hint: string) {
super(message);
}
}

function getDependency(dep: string): string {
let [scope, name] = dep.split('/');
if (scope.charAt(0) === '@') scope += '/' + name;
return scope;
}

function resolveWebDepFromCDN(dep: string): string {
const depFromDeps = getDependency(dep);
const deps = cwdManifest.dependencies || {};
const version = deps[depFromDeps] ? `@${deps[depFromDeps]}` : '';
return `${cdnUrl}/${depFromDeps}${version}?type=module`;
}

/**
* Resolve a "webDependencies" input value to the correct absolute file location.
* Supports both npm package names, and file paths relative to the node_modules directory.
Expand Down Expand Up @@ -86,9 +106,7 @@ function resolveWebDependency(dep: string): string {
return path.join(nodeModulesLoc, manifest.module);
}

throw new Error(
`Error loading "${dep}" at "${nodeModulesLoc}". (MODE=${dependencyStats.mode}) `,
);
throw new Error(`Error loading "${dep}" at "${nodeModulesLoc}". (MODE=${dependencyStats.mode}) `);
}

/**
Expand All @@ -99,15 +117,37 @@ function getWebDependencyName(dep: string): string {
return dep.replace(/\.js$/, '');
}

function safeWriter(file: string) {
file = path.normalize(file);
mkdirp.sync(path.dirname(file));
return fs.createWriteStream(file);
}

function download(uri: string, file: string) {
return new Promise((res, rej) => {
https
.get(uri, r => {
const code = r.statusCode;
if (code >= 400) return rej({code, message: r.statusMessage});
if (code > 300 && code < 400) {
return download(`${cdnUrl}${r.headers.location}`, file).then(res);
}
const write = safeWriter(file).on('finish', _ => res(file));
r.pipe(write);
})
.on('error', rej);
});
}

export async function install(
arrayOfDeps: string[],
{isCleanInstall, destLoc, skipFailures, isStrict, isOptimized}: InstallOptions,
{isCleanInstall, destLoc, skipFailures, isStrict, isOptimized, skipNpm}: InstallOptions,
) {
if (arrayOfDeps.length === 0) {
logError('no dependencies found.');
return;
}
if (!fs.existsSync(path.join(cwd, 'node_modules'))) {
if (!fs.existsSync(path.join(cwd, 'node_modules')) && !skipNpm) {
logError('no "node_modules" directory exists. Did you run "npm install" first?');
return;
}
Expand All @@ -120,7 +160,7 @@ export async function install(
for (const dep of arrayOfDeps) {
try {
const depName = getWebDependencyName(dep);
const depLoc = resolveWebDependency(dep);
const depLoc = skipNpm ? resolveWebDepFromCDN(dep) : resolveWebDependency(dep);
depObject[depName] = depLoc;
detectionResults.push([dep, true]);
spinner.text = banner + formatDetectionResults(skipFailures);
Expand All @@ -139,23 +179,34 @@ export async function install(
}
}

if (skipNpm) {
const promises = [];
for (const dep in depObject) {
const depPath = path.join(os.tmpdir(), 'pika-web', `${dep}.js`);
promises.push(download(depObject[dep], depPath));
depObject[dep] = depPath;
}
await Promise.all(promises);
}

const inputOptions = {
input: depObject,
plugins: [
!isStrict &&
rollupPluginReplace({
'process.env.NODE_ENV': isOptimized ? '"production"' : '"development"',
}),
rollupPluginNodeResolve({
module: true, // Default: true
jsnext: false, // Default: false
main: !isStrict, // Default: true
browser: false, // Default: false
modulesOnly: isStrict, // Default: false
extensions: ['.mjs', '.js', '.json'], // Default: [ '.mjs', '.js', '.json', '.node' ]
// whether to prefer built-in modules (e.g. `fs`, `path`) or local ones with the same names
preferBuiltins: false, // Default: true
}),
!skipNpm &&
rollupPluginNodeResolve({
module: true, // Default: true
jsnext: false, // Default: false
main: !isStrict, // Default: true
browser: false, // Default: false
modulesOnly: isStrict, // Default: false
extensions: ['.mjs', '.js', '.json'], // Default: [ '.mjs', '.js', '.json', '.node' ]
// whether to prefer built-in modules (e.g. `fs`, `path`) or local ones with the same names
preferBuiltins: false, // Default: true
}),
!isStrict &&
rollupPluginJson({
preferConst: true,
Expand All @@ -181,15 +232,21 @@ export async function install(
}

export async function cli(args: string[]) {
const {help, optimize = false, strict = false, clean = false, dest = 'web_modules'} = yargs(args);
const {
help,
optimize = false,
strict = false,
clean = false,
dest = 'web_modules',
skipNpm = false,
} = yargs(args);
const destLoc = path.join(cwd, dest);

if (help) {
showHelp();
process.exit(0);
}

const cwdManifest = require(path.join(cwd, 'package.json'));
const doesWhitelistExist = !!(
cwdManifest['@pika/web'] && cwdManifest['@pika/web'].webDependencies
);
Expand All @@ -204,10 +261,14 @@ export async function cli(args: string[]) {
skipFailures: !doesWhitelistExist,
isStrict: strict,
isOptimized: optimize,
skipNpm,
});
if (result) {
spinner.succeed(
chalk.bold(`@pika/web`) + ` installed: ` + formatDetectionResults(!doesWhitelistExist) + '. ' +
chalk.bold(`@pika/web`) +
` installed: ` +
formatDetectionResults(!doesWhitelistExist) +
'. ' +
chalk.dim(`[${((Date.now() - startTime) / 1000).toFixed(2)}s]`),
);
}
Expand Down