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

Automagically install missing dependancies #306

Merged
merged 17 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
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
25 changes: 18 additions & 7 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Search open/closed issues before submitting since someone might have asked the s
<!--- Provide a general summary of the issue in the title above -->

### 🎛 Configuration (.babelrc, package.json, cli command)

<!--- If describing a bug, tell us what your babel configuration looks like -->

```js
Expand All @@ -18,31 +19,41 @@ Search open/closed issues before submitting since someone might have asked the s
```

### 🤔 Expected Behavior

<!--- If you're describing a bug, tell us what should happen -->

<!--- If you're suggesting a change/improvement, tell us how it should work -->

### 😯 Current Behavior

<!--- If describing a bug, tell us what happens instead of the expected behavior -->

<!--- If you are seeing an error, please include the full error message and stack trace -->

<!--- If suggesting a change/improvement, explain the difference from current behavior -->

### 💁 Possible Solution

<!--- Not obligatory, but suggest a fix/reason for the bug, -->

<!--- or ideas how to implement the addition or change -->

### 🔦 Context

<!--- How has this issue affected you? What are you trying to accomplish? -->

<!--- Providing context helps us come up with a solution that is most useful in the real world -->

### 🌍 Your Environment

<!--- Include as many relevant details about the environment you experienced the bug in -->

| Software | Version(s)
| ---------------- | ----------
| Parcel |
| Node |
| npm/Yarn |
| Operating System |
| Software | Version(s) |
| ---------------- | ---------- |
| Parcel |
| Node |
| npm/Yarn |
| Operating System |

<!-- Love parcel? Please consider supporting our collective:
👉 https://opencollective.com/parcel/donate -->
👉 https://opencollective.com/parcel/donate -->
7 changes: 1 addition & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,24 @@ yarn test
[node]: https://nodejs.org/
[yarn]: https://yarnpkg.com/


## Financial contributions

We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/parcel).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.


## Credits


### Contributors

Thank you to all the people who have already contributed to parcel!
<a href="graphs/contributors"><img src="https://opencollective.com/parcel/contributors.svg?width=890" /></a>


### Backers

Thank you to all our backers! [[Become a backer](https://opencollective.com/parcel#backer)]

<a href="https://opencollective.com/parcel#backers" target="_blank"><img src="https://opencollective.com/parcel/backers.svg?width=890"></a>


### Sponsors

Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/parcel#sponsor))
Expand All @@ -71,4 +66,4 @@ Thank you to all our sponsors! (please ask your company to also support this ope
<a href="https://opencollective.com/parcel/sponsor/6/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/parcel/sponsor/7/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/parcel/sponsor/8/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/parcel/sponsor/9/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/parcel/sponsor/9/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/9/avatar.svg"></a>
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
```shell
yarn global add parcel-bundler
```

or with npm:

```shell
npm install -g parcel-bundler
```
Expand Down Expand Up @@ -97,14 +99,12 @@ All feedback and suggestions are welcome!
This project exists thanks to all the people who contribute. [[Contribute]](CONTRIBUTING.md).
<a href="graphs/contributors"><img src="https://opencollective.com/parcel/contributors.svg?width=890" /></a>


## Backers

Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/parcel#backer)]

<a href="https://opencollective.com/parcel#backers" target="_blank"><img src="https://opencollective.com/parcel/backers.svg?width=890"></a>


## Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/parcel#sponsor)]
Expand All @@ -120,8 +120,6 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/parcel/sponsor/8/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/parcel/sponsor/9/website" target="_blank"><img src="https://opencollective.com/parcel/sponsor/9/avatar.svg"></a>



## License

MIT
3 changes: 2 additions & 1 deletion src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,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 @@ -5,7 +5,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;
4 changes: 3 additions & 1 deletion test/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ describe('plugins', function() {
});

it('should load package.json from parent tree', async function() {
let b = await bundle(__dirname + '/integration/plugins/sub-folder/index.js');
let b = await bundle(
__dirname + '/integration/plugins/sub-folder/index.js'
);

assertBundleTree(b, {
name: 'index.js',
Expand Down