Skip to content

Commit

Permalink
Automagically install missing dependancies (#306)
Browse files Browse the repository at this point in the history
* run prettier

* initial try

* minor bugfixes

* fix secu vuln and add yarn support

* minor bugfix

* cleanup output

* test command instead of lockfile

* pivot back to finding the package/yarn file

* tiny cleanup
  • Loading branch information
Jasper De Moor authored and devongovett committed Dec 21, 2017
1 parent 56ef993 commit 6f493f0
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 25 deletions.
3 changes: 2 additions & 1 deletion src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ class Bundler extends EventEmitter {
let deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
for (let dep in deps) {
if (dep.startsWith('parcel-plugin-')) {
localRequire(dep, this.mainFile)(this);
let plugin = await localRequire(dep, this.mainFile);
plugin(this);
}
}
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion src/assets/CoffeeScriptAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const localRequire = require('../utils/localRequire');
class CoffeeScriptAsset extends JSAsset {
async parse(code) {
// require coffeescript, installed locally in the app
let coffee = localRequire('coffeescript', this.name);
let coffee = await localRequire('coffeescript', this.name);

// Transpile Module using CoffeeScript and parse result as ast format through babylon
this.contents = coffee.compile(code, {});
Expand Down
2 changes: 1 addition & 1 deletion src/assets/LESSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const promisify = require('../utils/promisify');
class LESSAsset extends CSSAsset {
async parse(code) {
// less should be installed locally in the module that's being required
let less = localRequire('less', this.name);
let less = await localRequire('less', this.name);
let render = promisify(less.render.bind(less));

let opts =
Expand Down
2 changes: 1 addition & 1 deletion src/assets/SASSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const path = require('path');
class SASSAsset extends CSSAsset {
async parse(code) {
// node-sass should be installed locally in the module that's being required
let sass = localRequire('node-sass', this.name);
let sass = await localRequire('node-sass', this.name);
let render = promisify(sass.render.bind(sass));

let opts =
Expand Down
13 changes: 8 additions & 5 deletions src/assets/StylusAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i;
class StylusAsset extends CSSAsset {
async parse(code) {
// stylus should be installed locally in the module that's being required
let stylus = localRequire('stylus', this.name);
let stylus = await localRequire('stylus', this.name);
let opts =
this.package.stylus ||
(await config.load(this.name, ['.stylusrc', '.stylusrc.js']));
let style = stylus(code, opts);
style.set('filename', this.name);
style.set('include css', true);
style.set('Evaluator', createEvaluator(this));
style.set('Evaluator', await createEvaluator(this));

// Setup a handler for the URL function so we add dependencies for linked assets.
style.define('url', node => {
Expand All @@ -38,9 +38,12 @@ class StylusAsset extends CSSAsset {
}
}

function createEvaluator(asset) {
const Evaluator = localRequire('stylus/lib/visitor/evaluator', asset.name);
const utils = localRequire('stylus/lib/utils', asset.name);
async function createEvaluator(asset) {
const Evaluator = await localRequire(
'stylus/lib/visitor/evaluator',
asset.name
);
const utils = await localRequire('stylus/lib/utils', asset.name);
const resolver = new Resolver(asset.options);

// This is a custom stylus evaluator that extends stylus with support for the node
Expand Down
2 changes: 1 addition & 1 deletion src/assets/TypeScriptAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const localRequire = require('../utils/localRequire');
class TypeScriptAsset extends JSAsset {
async parse(code) {
// require typescript, installed locally in the app
let typescript = localRequire('typescript', this.name);
let typescript = await localRequire('typescript', this.name);
let transpilerOptions = {
compilerOptions: {
module: typescript.ModuleKind.CommonJS,
Expand Down
7 changes: 3 additions & 4 deletions src/transforms/postcss.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ async function getConfig(asset) {
delete config.plugins['postcss-modules'];
}

config.plugins = loadPlugins(config.plugins, asset.name);
config.plugins = await loadPlugins(config.plugins, asset.name);

if (config.modules) {
config.plugins.push(
localRequire('postcss-modules', asset.name)(postcssModulesConfig)
);
let postcssModules = await localRequire('postcss-modules', asset.name);
config.plugins.push(postcssModules(postcssModulesConfig));
}

if (asset.options.minify) {
Expand Down
2 changes: 1 addition & 1 deletion src/transforms/posthtml.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async function getConfig(asset) {
}

config = config || {};
config.plugins = loadPlugins(config.plugins, asset.name);
config.plugins = await loadPlugins(config.plugins, asset.name);

if (asset.options.minify) {
config.plugins.push(htmlnano());
Expand Down
64 changes: 64 additions & 0 deletions src/utils/installPackage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const {spawn} = require('child_process');
const config = require('./config');
const path = require('path');

async function getLocation(dir) {
try {
return await config.resolve(dir, ['yarn.lock']);
} catch (e) {
try {
return await config.resolve(dir, ['package.json']);
} catch (e) {
// TODO: log a warning
return dir;
}
}
}

module.exports = async function(dir, name) {
let location = await getLocation(dir);

return new Promise((resolve, reject) => {
let install;
let options = {};

if (location.indexOf('yarn.lock') > -1) {
options.cwd = path.dirname(location);
install = spawn('yarn', ['add', name, '--dev'], options);
} else {
options.cwd = path.dirname(location);
install = spawn('npm', ['install', name, '--save-dev'], options);
}

install.stdout.on('data', data => {
// TODO: Log this using logger
data
.toString()
.split('\n')
.forEach(message => {
if (message !== '') {
console.log(message);
}
});
});

install.stderr.on('data', data => {
// TODO: Log this using logger
data
.toString()
.split('\n')
.forEach(message => {
if (message !== '') {
console.log(message);
}
});
});

install.on('close', code => {
if (code !== 0) {
return reject(new Error(`Failed to install ${name}.`));
}
return resolve();
});
});
};
19 changes: 12 additions & 7 deletions src/utils/loadPlugins.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
const localRequire = require('./localRequire');

module.exports = function loadPlugins(plugins, relative) {
module.exports = async function loadPlugins(plugins, relative) {
if (Array.isArray(plugins)) {
return plugins.map(p => loadPlugin(p, relative)).filter(Boolean);
return await Promise.all(
plugins.map(async p => await loadPlugin(p, relative)).filter(Boolean)
);
} else if (typeof plugins === 'object') {
return Object.keys(plugins)
.map(p => loadPlugin(p, relative, plugins[p]))
.filter(Boolean);
let mapPlugins = await Promise.all(
Object.keys(plugins).map(
async p => await loadPlugin(p, relative, plugins[p])
)
);
return mapPlugins.filter(Boolean);
} else {
return [];
}
};

function loadPlugin(plugin, relative, options) {
async function loadPlugin(plugin, relative, options) {
if (typeof plugin === 'string') {
plugin = localRequire(plugin, relative);
plugin = await localRequire(plugin, relative);
plugin = plugin.default || plugin;

if (typeof options !== 'object') {
Expand Down
19 changes: 16 additions & 3 deletions src/utils/localRequire.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
const {dirname} = require('path');
const resolve = require('resolve');
const install = require('./installPackage');

const cache = new Map();

module.exports = function(name, path) {
async function localRequire(name, path, triedInstall = false) {
let basedir = dirname(path);
let key = basedir + ':' + name;
let resolved = cache.get(key);
if (!resolved) {
resolved = resolve.sync(name, {basedir});
try {
resolved = resolve.sync(name, {basedir});
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND' && triedInstall === false) {
// TODO: Make this use logger
console.log(`INSTALLING ${name}...\n`);
await install(process.cwd(), name);
return localRequire(name, path, true);
}
throw e;
}
cache.set(key, resolved);
}

return require(resolved);
};
}

module.exports = localRequire;

0 comments on commit 6f493f0

Please sign in to comment.