From 9affe21d37a96f342a81e967139eedc154c53abf Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 2 Jan 2016 15:25:47 -0600 Subject: [PATCH 01/52] Add server-side example --- example/.gitignore | 1 + example/.npmrc | 2 ++ example/.nvmrc | 1 + example/README.md | 25 +++++++++++++++++++++++++ example/package.json | 12 ++++++++++++ example/server.js | 12 ++++++++++++ example/webpack.config.js | 37 +++++++++++++++++++++++++++++++++++++ 7 files changed, 90 insertions(+) create mode 100644 example/.gitignore create mode 100644 example/.npmrc create mode 100644 example/.nvmrc create mode 100644 example/README.md create mode 100644 example/package.json create mode 100644 example/server.js create mode 100644 example/webpack.config.js diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1 @@ +build diff --git a/example/.npmrc b/example/.npmrc new file mode 100644 index 0000000..f5357d5 --- /dev/null +++ b/example/.npmrc @@ -0,0 +1,2 @@ +save=true +save-exact=true diff --git a/example/.nvmrc b/example/.nvmrc new file mode 100644 index 0000000..32bc952 --- /dev/null +++ b/example/.nvmrc @@ -0,0 +1 @@ +v5.3.0 diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..aecd999 --- /dev/null +++ b/example/README.md @@ -0,0 +1,25 @@ +# `npm-install-loader` Example + +## Usage + +It's recommended to install the same version of Node this is tested against: + +```shell +$ nvm install +$ nvm use +``` + +Install dependencies required to start working: + +```shell +$ npm install +``` + +Start the server that (that doesn't do anything yet): + +```shell +$ npm start +``` + +Finally, make changes to `server.js` and watch dependencies get installed +as you go! diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..a14fa4a --- /dev/null +++ b/example/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "devDependencies": { + "reload-server-webpack-plugin": "1.0.1", + "webpack": "1.12.9" + }, + "scripts": { + "link": "cd .. && npm link . && cd - && npm link npm-install-loader", + "postinstall": "npm run link", + "start": "webpack --watch" + } +} diff --git a/example/server.js b/example/server.js new file mode 100644 index 0000000..61a3dd5 --- /dev/null +++ b/example/server.js @@ -0,0 +1,12 @@ +/** + * Uncomment things line-by-line & watch the loader go! + */ + +// var express = require("express"); +// +// express() +// .get("*", function(req, res) { +// res.send("Howdy!"); +// }) +// .listen(3000) +//; diff --git a/example/webpack.config.js b/example/webpack.config.js new file mode 100644 index 0000000..456c1b4 --- /dev/null +++ b/example/webpack.config.js @@ -0,0 +1,37 @@ +var path = require("path"); +var ReloadServerPlugin = require("reload-server-webpack-plugin"); +var webpack = require("webpack"); + +module.exports = { + context: __dirname, + + entry: { + server: path.join(__dirname, "server.js"), + }, + + externals: /^[a-z\-0-9]+$/, // Every non-relative module is external + + module: { + postLoaders: [ + { + exclude: /node_modules/, + loader: "npm-install-loader", + test: /\.js$/, + }, + ], + }, + + output: { + filename: "[name].min.js", + libraryTarget: "commonjs2", + path: path.join(__dirname, "build"), + }, + + plugins: [ + new ReloadServerPlugin({ + script: path.join(__dirname, "build/server.min.js"), + }), + ], + + target: "node" +} From 5dce1573fc9e0d31af19a60127c4b3b63ddc3ea4 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Mon, 4 Jan 2016 13:14:19 -0600 Subject: [PATCH 02/52] Add CSS for example --- example/client.js | 1 + example/layout.css | 1 + example/package.json | 2 + example/public/index.html | 8 ++++ example/webpack.config.client.js | 44 +++++++++++++++++++++ example/webpack.config.js | 37 ----------------- example/webpack.config.server.js | 68 ++++++++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 example/client.js create mode 100644 example/layout.css create mode 100644 example/public/index.html create mode 100644 example/webpack.config.client.js delete mode 100644 example/webpack.config.js create mode 100644 example/webpack.config.server.js diff --git a/example/client.js b/example/client.js new file mode 100644 index 0000000..7f813da --- /dev/null +++ b/example/client.js @@ -0,0 +1 @@ +require("./layout.css"); diff --git a/example/layout.css b/example/layout.css new file mode 100644 index 0000000..b51f5ef --- /dev/null +++ b/example/layout.css @@ -0,0 +1 @@ +@import url("~bootstrap"); diff --git a/example/package.json b/example/package.json index a14fa4a..b2c9e4c 100644 --- a/example/package.json +++ b/example/package.json @@ -1,7 +1,9 @@ { "private": true, "devDependencies": { + "css-loader": "0.23.1", "reload-server-webpack-plugin": "1.0.1", + "style-loader": "0.13.0", "webpack": "1.12.9" }, "scripts": { diff --git a/example/public/index.html b/example/public/index.html new file mode 100644 index 0000000..0321698 --- /dev/null +++ b/example/public/index.html @@ -0,0 +1,8 @@ + + +
+

Howdy!

+
+ + + diff --git a/example/webpack.config.client.js b/example/webpack.config.client.js new file mode 100644 index 0000000..3c80661 --- /dev/null +++ b/example/webpack.config.client.js @@ -0,0 +1,44 @@ +var path = require("path"); +var webpack = require("webpack"); + +module.exports = { + context: __dirname, + + devtool: "#inline-source-map", + + entry: [ + "webpack-hot-middleware/client?reload=true", + "./client.js", + ], + + module: { + loaders: [ + { + loader: "style-loader", + test: /\.css$/, + }, + { + loader: "npm-install-loader", + test: /\.css$/, + }, + { + loader: "css-loader", + test: /\.css$/, + }, + { + loader: "less-loader", + test: /\.less$/, + }, + ], + }, + + output: { + filename: "client.min.js", + path: path.join(__dirname, "/public/build"), + publicPath: "/build/" + }, + + plugins: [ + new webpack.HotModuleReplacementPlugin(), + ] +}; diff --git a/example/webpack.config.js b/example/webpack.config.js deleted file mode 100644 index 456c1b4..0000000 --- a/example/webpack.config.js +++ /dev/null @@ -1,37 +0,0 @@ -var path = require("path"); -var ReloadServerPlugin = require("reload-server-webpack-plugin"); -var webpack = require("webpack"); - -module.exports = { - context: __dirname, - - entry: { - server: path.join(__dirname, "server.js"), - }, - - externals: /^[a-z\-0-9]+$/, // Every non-relative module is external - - module: { - postLoaders: [ - { - exclude: /node_modules/, - loader: "npm-install-loader", - test: /\.js$/, - }, - ], - }, - - output: { - filename: "[name].min.js", - libraryTarget: "commonjs2", - path: path.join(__dirname, "build"), - }, - - plugins: [ - new ReloadServerPlugin({ - script: path.join(__dirname, "build/server.min.js"), - }), - ], - - target: "node" -} diff --git a/example/webpack.config.server.js b/example/webpack.config.server.js new file mode 100644 index 0000000..6983dd0 --- /dev/null +++ b/example/webpack.config.server.js @@ -0,0 +1,68 @@ +var path = require("path"); +var ReloadServerPlugin = require("reload-server-webpack-plugin"); +var webpack = require("webpack"); + +module.exports = { + context: process.cwd(), + + entry: { + server: path.join(process.cwd(), "server.js"), + }, + + externals: [ + /^[a-z\-0-9]+$/, // Every non-relative module is external + function(context, request, callback) { + if (/webpack\.config\./.test(request)) { + return callback(null, path.join(context, request)); + } + + callback(); + }, + ], + + module: { + loaders: [ + { + loader: "style-loader", + test: /\.css$/, + }, + { + loader: "css-loader", + test: /\.css$/, + }, + ], + + postLoaders: [ + { + exclude: /node_modules/, + loader: "npm-install-loader", + query: { + cli: { + save: true, + saveExact: false, + }, + }, + test: /\.js$/, + }, + ], + }, + + node: { + __filename: true, + __dirname: true, + }, + + output: { + filename: "[name].min.js", + libraryTarget: "commonjs2", + path: path.join(__dirname, "build"), + }, + + plugins: [ + new ReloadServerPlugin({ + script: path.join(__dirname, "build/server.min.js"), + }), + ], + + target: "node", +} From 52780fbd05d0710b7c5f70f30aa69abce4761ff2 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Mon, 4 Jan 2016 13:14:32 -0600 Subject: [PATCH 03/52] Improve server.js example --- example/README.md | 4 ++-- example/package.json | 2 +- example/server.js | 27 +++++++++++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/example/README.md b/example/README.md index aecd999..63ccb18 100644 --- a/example/README.md +++ b/example/README.md @@ -21,5 +21,5 @@ Start the server that (that doesn't do anything yet): $ npm start ``` -Finally, make changes to `server.js` and watch dependencies get installed -as you go! +Finally, open , make changes to `server.js` +and watch dependencies get installed as you go! diff --git a/example/package.json b/example/package.json index b2c9e4c..ed0a993 100644 --- a/example/package.json +++ b/example/package.json @@ -9,6 +9,6 @@ "scripts": { "link": "cd .. && npm link . && cd - && npm link npm-install-loader", "postinstall": "npm run link", - "start": "webpack --watch" + "start": "webpack --config webpack.config.server.js --watch" } } diff --git a/example/server.js b/example/server.js index 61a3dd5..1567970 100644 --- a/example/server.js +++ b/example/server.js @@ -2,11 +2,22 @@ * Uncomment things line-by-line & watch the loader go! */ -// var express = require("express"); -// -// express() -// .get("*", function(req, res) { -// res.send("Howdy!"); -// }) -// .listen(3000) -//; +var config = require("./webpack.config.client"); +var dev = require("webpack-dev-middleware"); +var express = require("express"); +var hot = require("webpack-hot-middleware"); +var path = require("path"); +var webpack = require("webpack"); + +var compiler = webpack(config); + +express() + .use(dev(compiler, { + noInfo: true, + publicPath: config.output.publicPath, + quiet: true, + })) + .use(hot(compiler, { reload: true })) + .use(express.static("public")) + .listen(3000) +; From 72b26947c8120168945e19d2da37de929fb9c382 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 9 Jan 2016 18:45:19 -0600 Subject: [PATCH 04/52] Rename to npm-install-webpack-plugin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d73f97..5b7ad4f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "npm-install-loader", + "name": "npm-install-webpack-plugin", "version": "1.1.1", "description": "Webpack loader to automatically npm install & save dependencies.", "main": "index.js", From 745c8990e684c47ee4962bf2fc407173d0d30d6e Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 9 Jan 2016 18:45:29 -0600 Subject: [PATCH 05/52] First pass at plugin --- index.js | 2 +- src/plugin.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/plugin.js diff --git a/index.js b/index.js index 61fe0ef..c12b35f 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require("./src/loader.js"); +module.exports = require("./src/plugin.js"); diff --git a/src/plugin.js b/src/plugin.js new file mode 100644 index 0000000..fec7b23 --- /dev/null +++ b/src/plugin.js @@ -0,0 +1,39 @@ +var installer = require("./installer"); + +function NpmInstallPlugin(options) { + this.options = options || {}; +} + +NpmInstallPlugin.prototype.apply = function(compiler) { + var cli = this.options.cli; + + compiler.plugin("normal-module-factory", function(factory) { + console.log("normal-module-factory"); + + factory.plugin("before-resolve", function(result, callback) { + var context = result.context; + var request = result.request; + + factory.resolvers.normal.resolve(context, request, function(err, filepath) { + if (filepath) { + return callback(null, result); + } + + var missing = installer.check([request], []); + + // Found in `require` paths + if (!missing.length) { + return callback(null, result); + } + + console.log("Missing dependency", result, missing); + + installer.install(missing, cli); + + return callback(null, result); + }); + }); + }); +}; + +module.exports = NpmInstallPlugin; From a719d1770ae889b77a33aab67767c1cacb24031a Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 9 Jan 2016 18:46:13 -0600 Subject: [PATCH 06/52] Update npm run link to use npm-install-webpack-plugin --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index ed0a993..907ce20 100644 --- a/example/package.json +++ b/example/package.json @@ -7,7 +7,7 @@ "webpack": "1.12.9" }, "scripts": { - "link": "cd .. && npm link . && cd - && npm link npm-install-loader", + "link": "(cd .. && npm link .) && npm link npm-install-webpack-plugin", "postinstall": "npm run link", "start": "webpack --config webpack.config.server.js --watch" } From c967c258cd9c779334f230207061840e73ac653a Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 9 Jan 2016 18:46:54 -0600 Subject: [PATCH 07/52] webpack.config uses NpmInstallPlugin --- example/webpack.config.client.js | 12 ++++++++---- example/webpack.config.server.js | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/example/webpack.config.client.js b/example/webpack.config.client.js index 3c80661..cc21400 100644 --- a/example/webpack.config.client.js +++ b/example/webpack.config.client.js @@ -1,3 +1,4 @@ +var NpmInstallPlugin = require("npm-install-webpack-plugin"); var path = require("path"); var webpack = require("webpack"); @@ -17,10 +18,6 @@ module.exports = { loader: "style-loader", test: /\.css$/, }, - { - loader: "npm-install-loader", - test: /\.css$/, - }, { loader: "css-loader", test: /\.css$/, @@ -39,6 +36,13 @@ module.exports = { }, plugins: [ + new NpmInstallPlugin({ + cli: { + save: true, + saveExact: true, + }, + }), + new webpack.HotModuleReplacementPlugin(), ] }; diff --git a/example/webpack.config.server.js b/example/webpack.config.server.js index 6983dd0..0f25105 100644 --- a/example/webpack.config.server.js +++ b/example/webpack.config.server.js @@ -1,3 +1,4 @@ +var NpmInstallPlugin = require("npm-install-webpack-plugin"); var path = require("path"); var ReloadServerPlugin = require("reload-server-webpack-plugin"); var webpack = require("webpack"); @@ -31,20 +32,6 @@ module.exports = { test: /\.css$/, }, ], - - postLoaders: [ - { - exclude: /node_modules/, - loader: "npm-install-loader", - query: { - cli: { - save: true, - saveExact: false, - }, - }, - test: /\.js$/, - }, - ], }, node: { @@ -59,6 +46,13 @@ module.exports = { }, plugins: [ + new NpmInstallPlugin({ + cli: { + save: true, + saveExact: true, + }, + }), + new ReloadServerPlugin({ script: path.join(__dirname, "build/server.min.js"), }), From 17f1fe0e12f72adb7dbe4a1269893da6571fc3b9 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 19:58:26 -0600 Subject: [PATCH 08/52] Remove .npmrc to test CLI options --- example/.npmrc | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 example/.npmrc diff --git a/example/.npmrc b/example/.npmrc deleted file mode 100644 index f5357d5..0000000 --- a/example/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -save=true -save-exact=true From 6c5a20adcb91154c0c6fd8ed8acf72732ec14728 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:00:18 -0600 Subject: [PATCH 09/52] Import bootswatch instead of bootstrap --- example/layout.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/layout.css b/example/layout.css index b51f5ef..b1a1c72 100644 --- a/example/layout.css +++ b/example/layout.css @@ -1 +1 @@ -@import url("~bootstrap"); +@import "~bootswatch/lumen/bootstrap.css"; From 6e716ae80a4a29e034b76b2e9b3a9570334d5093 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:04:12 -0600 Subject: [PATCH 10/52] Complex webpack builds --- example/package.json | 4 ++- example/webpack.config.client.js | 58 ++++++++++++++++++++++++++------ example/webpack.config.server.js | 6 ++-- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/example/package.json b/example/package.json index 907ce20..f4f702e 100644 --- a/example/package.json +++ b/example/package.json @@ -2,9 +2,11 @@ "private": true, "devDependencies": { "css-loader": "0.23.1", + "file-loader": "0.8.5", "reload-server-webpack-plugin": "1.0.1", "style-loader": "0.13.0", - "webpack": "1.12.9" + "url-loader": "0.5.7", + "webpack": "1.12.11" }, "scripts": { "link": "(cd .. && npm link .) && npm link npm-install-webpack-plugin", diff --git a/example/webpack.config.client.js b/example/webpack.config.client.js index cc21400..a9595b2 100644 --- a/example/webpack.config.client.js +++ b/example/webpack.config.client.js @@ -7,10 +7,12 @@ module.exports = { devtool: "#inline-source-map", - entry: [ - "webpack-hot-middleware/client?reload=true", - "./client.js", - ], + entry: { + client: [ + "webpack-hot-middleware/client?reload=true", + "./client.js", + ], + }, module: { loaders: [ @@ -20,19 +22,53 @@ module.exports = { }, { loader: "css-loader", + query: { + localIdentName: "[name]-[local]--[hash:base64:5]", + }, test: /\.css$/, }, { - loader: "less-loader", - test: /\.less$/, + loader: "url-loader", + query: { + mimetype: "application/font-woff", + }, + test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, + }, + { + loader: "url-loader", + query: { + mimetype: "application/octet-stream", + }, + test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, + }, + { + loader: "file-loader", + test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, + }, + { + loader: "url-loader", + query: { + mimetype: "image/svg+xml", + }, + test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, + }, + { + loader: "url-loader", + query: { + limit: 8192, // Inline base64 URLs for <= 8K images + }, + test: /\.(png|jpg)(\?v=\d+\.\d+\.\d+)?$/, }, ], }, output: { - filename: "client.min.js", - path: path.join(__dirname, "/public/build"), - publicPath: "/build/" + chunkFilename: "[id].[hash:5]-[chunkhash:7].js", + devtoolModuleFilenameTemplate: "[absolute-resource-path]", + filename: "[name].js", + path: path.join(__dirname, "build/client"), + publicPath: "/", + libaryTarget: "var", }, plugins: [ @@ -44,5 +80,7 @@ module.exports = { }), new webpack.HotModuleReplacementPlugin(), - ] + ], + + target: "web", }; diff --git a/example/webpack.config.server.js b/example/webpack.config.server.js index 0f25105..34b6a89 100644 --- a/example/webpack.config.server.js +++ b/example/webpack.config.server.js @@ -40,9 +40,9 @@ module.exports = { }, output: { - filename: "[name].min.js", + filename: "[name].js", libraryTarget: "commonjs2", - path: path.join(__dirname, "build"), + path: path.join(__dirname, "build/server"), }, plugins: [ @@ -54,7 +54,7 @@ module.exports = { }), new ReloadServerPlugin({ - script: path.join(__dirname, "build/server.min.js"), + script: path.join(__dirname, "build/server/server.js"), }), ], From 5ae4cdac0ff402b1776bedbf149e25e8cd975eef Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:04:16 -0600 Subject: [PATCH 11/52] Prettier example --- example/public/index.html | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/example/public/index.html b/example/public/index.html index 0321698..981766b 100644 --- a/example/public/index.html +++ b/example/public/index.html @@ -1,8 +1,31 @@ -
-

Howdy!

+
+ + +
+

+ It Works! +

+ +

+ Webpack has successfully compiled the application JS & CSS. +

+ + + View on Github + +
- + From a66b11612ddd2cb53fefbb2206db9a625144201d Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:04:25 -0600 Subject: [PATCH 12/52] Improve README --- example/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/README.md b/example/README.md index 63ccb18..681d361 100644 --- a/example/README.md +++ b/example/README.md @@ -1,4 +1,8 @@ -# `npm-install-loader` Example +# `npm-install-webpack-plugin` Example + +> This example shows how it's possible to start with a minimal project setup +> and rapidly develop by auto-installing both Javascript & CSS dependencies +> without ever leaving the editor. ## Usage From ca151aed42192b20e418c9af66dd77bee08222f2 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:04:34 -0600 Subject: [PATCH 13/52] Step-by-step server example --- example/server.js | 75 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/example/server.js b/example/server.js index 1567970..c35d23c 100644 --- a/example/server.js +++ b/example/server.js @@ -2,22 +2,59 @@ * Uncomment things line-by-line & watch the loader go! */ -var config = require("./webpack.config.client"); -var dev = require("webpack-dev-middleware"); -var express = require("express"); -var hot = require("webpack-hot-middleware"); -var path = require("path"); -var webpack = require("webpack"); - -var compiler = webpack(config); - -express() - .use(dev(compiler, { - noInfo: true, - publicPath: config.output.publicPath, - quiet: true, - })) - .use(hot(compiler, { reload: true })) - .use(express.static("public")) - .listen(3000) -; +// /** +// * (1) Let's install express & start a server +// */ +// +// var express = require("express"); +// var app = express(); +// +// app +// .use(express.static("build/client")) +// .use(express.static("public")) +// .listen(3000, function(err) { +// if (err) { +// console.error(err); +// return; +// } +// +// console.log("Listening on http://localhost:3000/"); +// }) +// ; + + +// /** +// * (2) Next, let's bring in a local dependency (which won't install anything) +// */ +// +// var config = require("./webpack.config.client"); + + +// /** +// * (3) Also, let's bring in an existing dependency (which won't install anything) +// */ +// +// var webpack = require("webpack") +// var compiler = webpack(config); + + +// /** +// * (4) Add webpack-dev-middleware +// */ +// +// app.use(require("webpack-dev-middleware")(compiler, { +// noInfo: true, +// publicPath: config.output.publicPath, +// quiet: false, +// })); + + +// /** +// * (5) Add webpack-hot-middleware +// */ +// +// app.use(require("webpack-hot-middleware")(compiler, { reload: true })); +// +// /** +// * (6) Now go open and see if it works! +// */ From e126e445fb7738ec7d2663ed390c3f19d9e774aa Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:05:01 -0600 Subject: [PATCH 14/52] Installer operates on one file at a time --- src/installer.js | 77 +++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/src/installer.js b/src/installer.js index 9e69c63..baae68f 100644 --- a/src/installer.js +++ b/src/installer.js @@ -4,57 +4,39 @@ var kebabCase = require("lodash.kebabcase"); var path = require("path"); var util = require("util"); -module.exports.check = function(dependencies, dirs) { - var missing = []; - dependencies.forEach(function(dependency) { - // Ignore relative modules, which aren't installed by NPM - if (/^\./.test(dependency)) { - return; - } - - // Only look for the dependency directory - dependency = dependency.split('/')[0]; - - // Bail early if we've already determined this is a missing dependency - if (missing.indexOf(dependency) !== -1) { - return; - } - - try { - // Ignore dependencies that are resolveable - require.resolve(dependency); +module.exports.check = function(request) { + // Only look for the dependency directory + // @TODO Support namespaced NPM modules (e.g. @cycle/dom) + var dep = request.split("/").shift().toLowerCase(); + + // Ignore relative modules, which aren't installed by NPM + if (!dep.match(/^[a-z\-0-9]+$/)) { + return; + } - return; - } catch(e) { - var modulePaths = (dirs || []).map(function(dir) { - return path.resolve(dir, dependency); - }); + try { + var packagePath = require.resolve(path.join(process.cwd(), "package.json")); + var package = require(packagePath); - // Check all module directories for dependency directory - while (modulePaths.length) { - var modulePath = modulePaths.shift(); + // Remove cached copy for future checks + delete require.cache[packagePath]; + } catch(e) { + throw e; + } - try { - // If it exists, Webpack can find it - fs.statSync(modulePath); + var hasDep = package.dependencies && package.dependencies[dep]; + var hasDevDep = package.devDependencies && package.devDependencies[dep]; - return; - } catch(e) {} - } + // Bail early if we've already installed this dependency + if (hasDep || hasDevDep) { + return; + } - // Dependency must be missing - missing.push(dependency); - } - }); - return missing; + return dep; } -module.exports.install = function install(dependencies, options) { - if (!dependencies || !dependencies.length) { - return undefined; - } - - var args = ["install"].concat(dependencies); +module.exports.install = function install(dep, options) { + var args = ["install"].concat([dep]).filter(Boolean); if (options) { for (option in options) { @@ -73,8 +55,9 @@ module.exports.install = function install(dependencies, options) { } } - var suffix = dependencies.length === 1 ? "y" : "ies"; - console.info("Installing missing dependenc%s %s...", suffix, dependencies.join(", ")); + console.info("Installing missing dependency `%s`...", dep); + + var output = spawn.sync("npm", args, { stdio: "inherit" }); - return spawn.sync("npm", args, { stdio: "inherit" }); + return output; }; From ec28fb14cd0d992e8a62abc1a694f17272cb17a6 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 20:06:10 -0600 Subject: [PATCH 15/52] Refactor plugin --- src/plugin.js | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/plugin.js b/src/plugin.js index fec7b23..06481a3 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,3 +1,5 @@ +var path = require("path"); + var installer = require("./installer"); function NpmInstallPlugin(options) { @@ -7,30 +9,26 @@ function NpmInstallPlugin(options) { NpmInstallPlugin.prototype.apply = function(compiler) { var cli = this.options.cli; - compiler.plugin("normal-module-factory", function(factory) { - console.log("normal-module-factory"); - - factory.plugin("before-resolve", function(result, callback) { - var context = result.context; - var request = result.request; - - factory.resolvers.normal.resolve(context, request, function(err, filepath) { - if (filepath) { - return callback(null, result); - } + compiler.resolvers.normal.plugin("module", function(result, next) { + if (result.path.match("node_modules")) { + return next(); + } - var missing = installer.check([request], []); + var dep = installer.check(result.request); - // Found in `require` paths - if (!missing.length) { - return callback(null, result); - } + // Dependency needs to be installed + if (dep) { + installer.install(dep, cli); + } - console.log("Missing dependency", result, missing); - - installer.install(missing, cli); + next(); + }); - return callback(null, result); + compiler.plugin("normal-module-factory", function(factory) { + factory.plugin("before-resolve", function(result, next) { + // Trigger early-module resolution + factory.resolvers.normal.resolve(result.context, result.request, function(err, filepath) { + next(null, result); }); }); }); From 31aa6319c4f1a116a83b5e28ef744460f8045c62 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Fri, 22 Jan 2016 22:40:06 -0600 Subject: [PATCH 16/52] Update to Node v5.5.0 --- example/.nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/.nvmrc b/example/.nvmrc index 32bc952..268fccb 100644 --- a/example/.nvmrc +++ b/example/.nvmrc @@ -1 +1 @@ -v5.3.0 +v5.5.0 From a30433f8eab2733475b31b320c2a6ab0ad6efdbc Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 00:53:27 -0600 Subject: [PATCH 17/52] Loaders are always installed. Sub-modules are not. --- src/plugin.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/plugin.js b/src/plugin.js index 06481a3..d0f794a 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -6,23 +6,29 @@ function NpmInstallPlugin(options) { this.options = options || {}; } +NpmInstallPlugin.prototype.installModule = function(result, next) { + var dep = installer.check(result.request); + + if (dep) { + installer.install(dep, this.options.cli); + } + + next(); +}; + NpmInstallPlugin.prototype.apply = function(compiler) { - var cli = this.options.cli; + // Install loaders on demand + compiler.resolvers.loader.plugin("module", this.installModule.bind(this)); + // Install project dependencies on demand compiler.resolvers.normal.plugin("module", function(result, next) { + // Skip dependencies of dependencies if (result.path.match("node_modules")) { return next(); } - var dep = installer.check(result.request); - - // Dependency needs to be installed - if (dep) { - installer.install(dep, cli); - } - - next(); - }); + this.installModule(result, next); + }.bind(this)); compiler.plugin("normal-module-factory", function(factory) { factory.plugin("before-resolve", function(result, next) { From 16d713d68d073aada8611b96c9adbf658ad2e56d Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 00:53:45 -0600 Subject: [PATCH 18/52] Fix strict mode error with "package" --- src/installer.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/installer.js b/src/installer.js index baae68f..1afe7e2 100644 --- a/src/installer.js +++ b/src/installer.js @@ -15,17 +15,18 @@ module.exports.check = function(request) { } try { - var packagePath = require.resolve(path.join(process.cwd(), "package.json")); - var package = require(packagePath); + var pkgPath = require.resolve(path.join(process.cwd(), "package.json")); + var pkg = require(pkgPath); // Remove cached copy for future checks - delete require.cache[packagePath]; + delete require.cache[pkgPath]; } catch(e) { + console.error(e); throw e; } - var hasDep = package.dependencies && package.dependencies[dep]; - var hasDevDep = package.devDependencies && package.devDependencies[dep]; + var hasDep = pkg.dependencies && pkg.dependencies[dep]; + var hasDevDep = pkg.devDependencies && pkg.devDependencies[dep]; // Bail early if we've already installed this dependency if (hasDep || hasDevDep) { From d19aeb66c86a3cb39083d2bd9edb948a3f1722b5 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 00:54:38 -0600 Subject: [PATCH 19/52] Ignore global modules (e.g. "path") --- src/installer.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/installer.js b/src/installer.js index 1afe7e2..773e2de 100644 --- a/src/installer.js +++ b/src/installer.js @@ -4,13 +4,15 @@ var kebabCase = require("lodash.kebabcase"); var path = require("path"); var util = require("util"); +var EXTERNAL = /^[a-z\-0-9]+$/; + module.exports.check = function(request) { // Only look for the dependency directory // @TODO Support namespaced NPM modules (e.g. @cycle/dom) var dep = request.split("/").shift().toLowerCase(); // Ignore relative modules, which aren't installed by NPM - if (!dep.match(/^[a-z\-0-9]+$/)) { + if (!dep.match(EXTERNAL)) { return; } @@ -33,6 +35,16 @@ module.exports.check = function(request) { return; } + try { + var resolved = require.resolve(dep); + + if (resolved.match(EXTERNAL)) { + return; + } + } catch(e) { + // Module is not resolveable + } + return dep; } From ff16b2032a6ce04b1621a3baf0b31464cf8db0b5 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 00:55:50 -0600 Subject: [PATCH 20/52] Add babel + webpack --- example/.babelrc | 8 +++ example/package.json | 11 ++-- example/webpack.config.babel.js | 39 ++++++++++++ example/webpack.config.client.babel.js | 29 +++++++++ example/webpack.config.client.js | 86 -------------------------- example/webpack.config.server.babel.js | 31 ++++++++++ example/webpack.config.server.js | 62 ------------------- 7 files changed, 113 insertions(+), 153 deletions(-) create mode 100644 example/.babelrc create mode 100644 example/webpack.config.babel.js create mode 100644 example/webpack.config.client.babel.js delete mode 100644 example/webpack.config.client.js create mode 100644 example/webpack.config.server.babel.js delete mode 100644 example/webpack.config.server.js diff --git a/example/.babelrc b/example/.babelrc new file mode 100644 index 0000000..add9397 --- /dev/null +++ b/example/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["react", "es2015", "stage-0"], + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} diff --git a/example/package.json b/example/package.json index f4f702e..16db388 100644 --- a/example/package.json +++ b/example/package.json @@ -1,16 +1,17 @@ { "private": true, "devDependencies": { - "css-loader": "0.23.1", - "file-loader": "0.8.5", + "babel-preset-es2015": "6.3.13", + "babel-preset-react": "6.3.13", + "babel-preset-react-hmre": "1.0.1", + "babel-preset-stage-0": "6.3.13", + "react": "0.14.6", "reload-server-webpack-plugin": "1.0.1", - "style-loader": "0.13.0", - "url-loader": "0.5.7", "webpack": "1.12.11" }, "scripts": { "link": "(cd .. && npm link .) && npm link npm-install-webpack-plugin", "postinstall": "npm run link", - "start": "webpack --config webpack.config.server.js --watch" + "start": "webpack --config webpack.config.server.babel.js --watch" } } diff --git a/example/webpack.config.babel.js b/example/webpack.config.babel.js new file mode 100644 index 0000000..6f9dfe2 --- /dev/null +++ b/example/webpack.config.babel.js @@ -0,0 +1,39 @@ +import NpmInstallPlugin from "npm-install-webpack-plugin"; +import path from "path"; + +export const defaults = { + context: process.cwd(), + + externals: [ + "npm-install-webpack-plugin", // Symlinked project, + ], + + module: { + loaders: [ + { test: /\.css$/, loader: "style-loader" }, + { test: /\.css$/, loader: "css-loader", query: { localIdentName: "[name]-[local]--[hash:base64:5]" } }, + { test: /\.eot$/, loader: "file-loader" }, + { test: /\.js$/, loader: "babel-loader", query: { cacheDirectory: true } }, + { test: /\.json$/, loader: "json-loader" }, + { test: /\.(png|jpg)$/, loader: "url-loader", query: { limit: 8192 } }, // Inline base64 URLs for <= 8K images + { test: /\.svg$/, loader: "url-loader", query: { mimetype: "image/svg+xml" } }, + { test: /\.ttf$/, loader: "url-loader", query: { mimetype: "application/octet-stream" } }, + { test: /\.(woff|woff2)$/, loader: "url-loader", query: { mimetype: "application/font-woff" } }, + ], + }, + + output: { + chunkFilename: "[id].[hash:5]-[chunkhash:7].js", + devtoolModuleFilenameTemplate: "[absolute-resource-path]", + filename: "[name].js", + }, + + plugins: [ + new NpmInstallPlugin({ + cli: { + save: true, + saveExact: true, + }, + }), + ], +}; diff --git a/example/webpack.config.client.babel.js b/example/webpack.config.client.babel.js new file mode 100644 index 0000000..0fe4308 --- /dev/null +++ b/example/webpack.config.client.babel.js @@ -0,0 +1,29 @@ +import path from "path"; +import webpack from "webpack"; + +import { defaults } from "./webpack.config.babel"; + +export default { + ...defaults, + + entry: { + client: [ + "webpack-hot-middleware/client?reload=true", + "./src/client.js", + ], + }, + + output: { + ...defaults.output, + libaryTarget: "var", + path: path.join(defaults.context, "build/client"), + publicPath: "/", + }, + + plugins: [ + ...defaults.plugins, + new webpack.HotModuleReplacementPlugin(), + ], + + target: "web", +} diff --git a/example/webpack.config.client.js b/example/webpack.config.client.js deleted file mode 100644 index a9595b2..0000000 --- a/example/webpack.config.client.js +++ /dev/null @@ -1,86 +0,0 @@ -var NpmInstallPlugin = require("npm-install-webpack-plugin"); -var path = require("path"); -var webpack = require("webpack"); - -module.exports = { - context: __dirname, - - devtool: "#inline-source-map", - - entry: { - client: [ - "webpack-hot-middleware/client?reload=true", - "./client.js", - ], - }, - - module: { - loaders: [ - { - loader: "style-loader", - test: /\.css$/, - }, - { - loader: "css-loader", - query: { - localIdentName: "[name]-[local]--[hash:base64:5]", - }, - test: /\.css$/, - }, - { - loader: "url-loader", - query: { - mimetype: "application/font-woff", - }, - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - }, - { - loader: "url-loader", - query: { - mimetype: "application/octet-stream", - }, - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - }, - { - loader: "file-loader", - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - }, - { - loader: "url-loader", - query: { - mimetype: "image/svg+xml", - }, - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - }, - { - loader: "url-loader", - query: { - limit: 8192, // Inline base64 URLs for <= 8K images - }, - test: /\.(png|jpg)(\?v=\d+\.\d+\.\d+)?$/, - }, - ], - }, - - output: { - chunkFilename: "[id].[hash:5]-[chunkhash:7].js", - devtoolModuleFilenameTemplate: "[absolute-resource-path]", - filename: "[name].js", - path: path.join(__dirname, "build/client"), - publicPath: "/", - libaryTarget: "var", - }, - - plugins: [ - new NpmInstallPlugin({ - cli: { - save: true, - saveExact: true, - }, - }), - - new webpack.HotModuleReplacementPlugin(), - ], - - target: "web", -}; diff --git a/example/webpack.config.server.babel.js b/example/webpack.config.server.babel.js new file mode 100644 index 0000000..36fdca7 --- /dev/null +++ b/example/webpack.config.server.babel.js @@ -0,0 +1,31 @@ +import ReloadServerPlugin from "reload-server-webpack-plugin"; +import path from "path"; + +import { defaults } from "./webpack.config.babel"; + +export default { + ...defaults, + + entry: { + server: "./src/server.js", + }, + + externals: [ + ...defaults.externals, + // Every non-relative module is external + /^[a-z\-0-9]+$/, + ], + + output: { + ...defaults.output, + libraryTarget: "commonjs2", + path: path.join(defaults.context, "build/server"), + }, + + plugins: [ + ...defaults.plugins, + new ReloadServerPlugin({ script: "./build/server/server.js" }), + ], + + target: "node", +} diff --git a/example/webpack.config.server.js b/example/webpack.config.server.js deleted file mode 100644 index 34b6a89..0000000 --- a/example/webpack.config.server.js +++ /dev/null @@ -1,62 +0,0 @@ -var NpmInstallPlugin = require("npm-install-webpack-plugin"); -var path = require("path"); -var ReloadServerPlugin = require("reload-server-webpack-plugin"); -var webpack = require("webpack"); - -module.exports = { - context: process.cwd(), - - entry: { - server: path.join(process.cwd(), "server.js"), - }, - - externals: [ - /^[a-z\-0-9]+$/, // Every non-relative module is external - function(context, request, callback) { - if (/webpack\.config\./.test(request)) { - return callback(null, path.join(context, request)); - } - - callback(); - }, - ], - - module: { - loaders: [ - { - loader: "style-loader", - test: /\.css$/, - }, - { - loader: "css-loader", - test: /\.css$/, - }, - ], - }, - - node: { - __filename: true, - __dirname: true, - }, - - output: { - filename: "[name].js", - libraryTarget: "commonjs2", - path: path.join(__dirname, "build/server"), - }, - - plugins: [ - new NpmInstallPlugin({ - cli: { - save: true, - saveExact: true, - }, - }), - - new ReloadServerPlugin({ - script: path.join(__dirname, "build/server/server.js"), - }), - ], - - target: "node", -} From 0329237ae553611a33f3f5b2e11accdd213247d3 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 00:56:22 -0600 Subject: [PATCH 21/52] Convert example to ES2016 --- example/client.js | 1 - example/server.js | 60 ----------------------------- example/src/client.js | 1 + example/{ => src}/layout.css | 0 example/{ => src}/public/index.html | 0 example/src/server.js | 26 +++++++++++++ 6 files changed, 27 insertions(+), 61 deletions(-) delete mode 100644 example/client.js delete mode 100644 example/server.js create mode 100644 example/src/client.js rename example/{ => src}/layout.css (100%) rename example/{ => src}/public/index.html (100%) create mode 100644 example/src/server.js diff --git a/example/client.js b/example/client.js deleted file mode 100644 index 7f813da..0000000 --- a/example/client.js +++ /dev/null @@ -1 +0,0 @@ -require("./layout.css"); diff --git a/example/server.js b/example/server.js deleted file mode 100644 index c35d23c..0000000 --- a/example/server.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Uncomment things line-by-line & watch the loader go! - */ - -// /** -// * (1) Let's install express & start a server -// */ -// -// var express = require("express"); -// var app = express(); -// -// app -// .use(express.static("build/client")) -// .use(express.static("public")) -// .listen(3000, function(err) { -// if (err) { -// console.error(err); -// return; -// } -// -// console.log("Listening on http://localhost:3000/"); -// }) -// ; - - -// /** -// * (2) Next, let's bring in a local dependency (which won't install anything) -// */ -// -// var config = require("./webpack.config.client"); - - -// /** -// * (3) Also, let's bring in an existing dependency (which won't install anything) -// */ -// -// var webpack = require("webpack") -// var compiler = webpack(config); - - -// /** -// * (4) Add webpack-dev-middleware -// */ -// -// app.use(require("webpack-dev-middleware")(compiler, { -// noInfo: true, -// publicPath: config.output.publicPath, -// quiet: false, -// })); - - -// /** -// * (5) Add webpack-hot-middleware -// */ -// -// app.use(require("webpack-hot-middleware")(compiler, { reload: true })); -// -// /** -// * (6) Now go open and see if it works! -// */ diff --git a/example/src/client.js b/example/src/client.js new file mode 100644 index 0000000..ddc3450 --- /dev/null +++ b/example/src/client.js @@ -0,0 +1 @@ +import "./layout.css"; diff --git a/example/layout.css b/example/src/layout.css similarity index 100% rename from example/layout.css rename to example/src/layout.css diff --git a/example/public/index.html b/example/src/public/index.html similarity index 100% rename from example/public/index.html rename to example/src/public/index.html diff --git a/example/src/server.js b/example/src/server.js new file mode 100644 index 0000000..f604f61 --- /dev/null +++ b/example/src/server.js @@ -0,0 +1,26 @@ +// import express from "express"; +// import webpack from "webpack"; +// +// import client from "../webpack.config.client.babel"; +// +// const app = express(); +// const compiler = webpack(client); +// +// app +// .use(express.static("build/client")) +// .use(express.static("src/public")) +// .use(require("webpack-dev-middleware")(compiler, { +// noInfo: true, +// publicPath: client.output.publicPath, +// quiet: false, +// })) +// .use(require("webpack-hot-middleware")(compiler, { reload: true })) +// .listen(3000, (err) => { +// if (err) { +// console.error(err); +// return; +// } +// +// console.log("Listening on http://localhost:3000/"); +// }) +// ; From 31fb1c0faca47dd865e5dd991e9ab5b5b9ddc850 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:04:59 -0600 Subject: [PATCH 22/52] Ignore symlinked modules --- src/installer.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/installer.js b/src/installer.js index 773e2de..6ef04f4 100644 --- a/src/installer.js +++ b/src/installer.js @@ -35,6 +35,17 @@ module.exports.check = function(request) { return; } + // Ignore linked modules + try { + var stats = fs.lstatSync(path.join(process.cwd(), "node_modules", dep)); + + if (stats.isSymbolicLink()) { + return; + } + } catch(e) { + // Module exists in node_modules, but isn't symlinked + } + try { var resolved = require.resolve(dep); From 90ada63f2815eba313be360d82c176eadabd477b Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:18:33 -0600 Subject: [PATCH 23/52] Convert example to React --- example/src/client.js | 7 +++- .../src/{layout.css => components/App.css} | 0 example/src/components/App.js | 36 +++++++++++++++++++ example/src/public/index.html | 27 ++------------ 4 files changed, 44 insertions(+), 26 deletions(-) rename example/src/{layout.css => components/App.css} (100%) create mode 100644 example/src/components/App.js diff --git a/example/src/client.js b/example/src/client.js index ddc3450..1d2c860 100644 --- a/example/src/client.js +++ b/example/src/client.js @@ -1 +1,6 @@ -import "./layout.css"; +import React from "react"; +import DOM from "react-dom"; + +import App from "./components/app"; + +DOM.render(, document.getElementById("app")); diff --git a/example/src/layout.css b/example/src/components/App.css similarity index 100% rename from example/src/layout.css rename to example/src/components/App.css diff --git a/example/src/components/App.js b/example/src/components/App.js new file mode 100644 index 0000000..4f96a77 --- /dev/null +++ b/example/src/components/App.js @@ -0,0 +1,36 @@ +import React from "react"; + +import "./App.css"; + +export default class App extends React.Component { + render() { + return ( +
+
+

+ + npm-install-webpack-plugin + +

+
+ +
+

+ It Works! +

+ +

+ Webpack has successfully compiled the application JS & CSS. +

+ + + View on Github + +
+
+ ); + } +} diff --git a/example/src/public/index.html b/example/src/public/index.html index 981766b..5ab23d3 100644 --- a/example/src/public/index.html +++ b/example/src/public/index.html @@ -1,30 +1,7 @@ -
- - -
-

- It Works! -

- -

- Webpack has successfully compiled the application JS & CSS. -

- - - View on Github - -
+
+ Waiting on client.js to load...
From f0cd8564bbbb9aa2b3ce568fbb62c35747296cc3 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:18:47 -0600 Subject: [PATCH 24/52] babel-loader ignores node_modules --- example/webpack.config.babel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/webpack.config.babel.js b/example/webpack.config.babel.js index 6f9dfe2..b3eaf6a 100644 --- a/example/webpack.config.babel.js +++ b/example/webpack.config.babel.js @@ -13,7 +13,7 @@ export const defaults = { { test: /\.css$/, loader: "style-loader" }, { test: /\.css$/, loader: "css-loader", query: { localIdentName: "[name]-[local]--[hash:base64:5]" } }, { test: /\.eot$/, loader: "file-loader" }, - { test: /\.js$/, loader: "babel-loader", query: { cacheDirectory: true } }, + { test: /\.js$/, loader: "babel-loader", query: { cacheDirectory: true }, exclude: /node_modules/ }, { test: /\.json$/, loader: "json-loader" }, { test: /\.(png|jpg)$/, loader: "url-loader", query: { limit: 8192 } }, // Inline base64 URLs for <= 8K images { test: /\.svg$/, loader: "url-loader", query: { mimetype: "image/svg+xml" } }, From 7cd60ef283d461f939c73ca5eefc101842f934f4 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:19:08 -0600 Subject: [PATCH 25/52] Default to .npmrc for installs (faster with cache-min!) --- example/.npmrc | 3 +++ example/webpack.config.babel.js | 11 ++--------- 2 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 example/.npmrc diff --git a/example/.npmrc b/example/.npmrc new file mode 100644 index 0000000..1cf309b --- /dev/null +++ b/example/.npmrc @@ -0,0 +1,3 @@ +cache-min=9999999 +save=true, +save-exact=true diff --git a/example/webpack.config.babel.js b/example/webpack.config.babel.js index b3eaf6a..0c50a9e 100644 --- a/example/webpack.config.babel.js +++ b/example/webpack.config.babel.js @@ -4,9 +4,7 @@ import path from "path"; export const defaults = { context: process.cwd(), - externals: [ - "npm-install-webpack-plugin", // Symlinked project, - ], + externals: [], module: { loaders: [ @@ -29,11 +27,6 @@ export const defaults = { }, plugins: [ - new NpmInstallPlugin({ - cli: { - save: true, - saveExact: true, - }, - }), + new NpmInstallPlugin(), ], }; From 47d79dbac697675fa55fc06e2bd0537e1d252fbf Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:21:33 -0600 Subject: [PATCH 26/52] Uncomment server --- example/src/server.js | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/example/src/server.js b/example/src/server.js index f604f61..9dfc6fc 100644 --- a/example/src/server.js +++ b/example/src/server.js @@ -1,26 +1,26 @@ -// import express from "express"; -// import webpack from "webpack"; -// -// import client from "../webpack.config.client.babel"; -// -// const app = express(); -// const compiler = webpack(client); -// -// app -// .use(express.static("build/client")) -// .use(express.static("src/public")) -// .use(require("webpack-dev-middleware")(compiler, { -// noInfo: true, -// publicPath: client.output.publicPath, -// quiet: false, -// })) -// .use(require("webpack-hot-middleware")(compiler, { reload: true })) -// .listen(3000, (err) => { -// if (err) { -// console.error(err); -// return; -// } -// -// console.log("Listening on http://localhost:3000/"); -// }) -// ; +import express from "express"; +import webpack from "webpack"; + +import client from "../webpack.config.client.babel"; + +const app = express(); +const compiler = webpack(client); + +app + .use(express.static("build/client")) + .use(express.static("src/public")) + .use(require("webpack-dev-middleware")(compiler, { + noInfo: true, + publicPath: client.output.publicPath, + quiet: false, + })) + .use(require("webpack-hot-middleware")(compiler, { reload: true })) + .listen(3000, (err) => { + if (err) { + console.error(err); + return; + } + + console.log("Listening on http://localhost:3000/"); + }) +; From 56d719ef9c6cd4bc94c5fb269152f9686e76b62b Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 01:29:57 -0600 Subject: [PATCH 27/52] Remove React as a devDependency --- example/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/example/package.json b/example/package.json index 16db388..e0191a7 100644 --- a/example/package.json +++ b/example/package.json @@ -5,7 +5,6 @@ "babel-preset-react": "6.3.13", "babel-preset-react-hmre": "1.0.1", "babel-preset-stage-0": "6.3.13", - "react": "0.14.6", "reload-server-webpack-plugin": "1.0.1", "webpack": "1.12.11" }, From a8cb2d566dc181dab59f518e1daf35c2cb0620d1 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:39:14 -0600 Subject: [PATCH 28/52] React is a dependency --- example/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/package.json b/example/package.json index e0191a7..8239457 100644 --- a/example/package.json +++ b/example/package.json @@ -12,5 +12,8 @@ "link": "(cd .. && npm link .) && npm link npm-install-webpack-plugin", "postinstall": "npm run link", "start": "webpack --config webpack.config.server.babel.js --watch" + }, + "dependencies": { + "react": "0.14.6", } } From eed1ba0e730903140e1d1776ef528497c56d483b Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:39:34 -0600 Subject: [PATCH 29/52] Remove parser & test --- src/parser.js | 30 -------------------- test/parser.test.js | 67 --------------------------------------------- 2 files changed, 97 deletions(-) delete mode 100644 src/parser.js delete mode 100644 test/parser.test.js diff --git a/src/parser.js b/src/parser.js deleted file mode 100644 index ba74cde..0000000 --- a/src/parser.js +++ /dev/null @@ -1,30 +0,0 @@ -var babel = require("babel-core"); - -function parseNode(node) { - if (!node) { - return; - } - - if (node.type === "CallExpression" && node.callee.name === "require") { - return node.arguments[0].value; - } - - if (node.type === "ExpressionStatement") { - return parseNode(node.expression); - } - - if (node.type === "ImportDeclaration") { - return node.source.value; - } - - if (node.type === "VariableDeclaration") { - return parseNode(node.declarations[0].init); - } -}; - -module.exports.parse = function parse(source) { - var result = babel.transform(source); - var dependencies = result.ast.program.body.map(parseNode).filter(Boolean); - - return dependencies; -}; diff --git a/test/parser.test.js b/test/parser.test.js deleted file mode 100644 index 65a09b0..0000000 --- a/test/parser.test.js +++ /dev/null @@ -1,67 +0,0 @@ -var babel = require("babel-core"); -var expect = require("expect"); -var parse = require("../src/parser").parse; - -describe("parser", function() { - describe(".parse", function() { - context("given statements", function() { - it("should return []", function() { - expect(parse(null)).toEqual([]); - expect(parse(undefined)).toEqual([]); - expect(parse(0)).toEqual([]); - expect(parse("")).toEqual([]); - expect(parse("var foo = 'bar';")).toEqual([]); - }); - }); - - context("given source that with NULLs in the AST", function() { - beforeEach(function() { - this.transform = expect.spyOn(babel, "transform").andReturn({ - ast: { - program: { - body: [null], - }, - }, - }); - }); - - afterEach(function() { - this.transform.restore(); - }); - - it("should return []", function() { - expect(parse("something with NULLs")).toEqual([]); - }); - }); - - context("given: require('foo')", function() { - it("should return 'foo'", function() { - expect(parse("require('foo')")).toEqual(["foo"]); - }); - }); - - context("given: let a = require('foo')", function() { - it("should return 'foo'", function() { - expect(parse("let a = require('foo')")).toEqual(["foo"]); - }); - }); - - context("given: const { a, b } = require('foo')", function() { - it("should return 'foo'", function() { - expect(parse("const { a, b } = require('foo')")).toEqual(["foo"]); - }); - }); - - context("given: import * as a from 'foo'", function() { - it("should return 'foo'", function() { - expect(parse("import * as a from 'foo'")).toEqual(["foo"]); - }); - }); - - context("given: import a from 'foo'", function() { - it("should return 'foo'", function() { - expect(parse("import a from 'foo'")).toEqual(["foo"]); - }); - }); - }); -}); From d7044c348fed621314a52d63a971160fb2b462ac Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:39:44 -0600 Subject: [PATCH 30/52] Remove loader & test --- src/loader.js | 30 ------------------ test/loader.test.js | 74 --------------------------------------------- 2 files changed, 104 deletions(-) delete mode 100644 src/loader.js delete mode 100644 test/loader.test.js diff --git a/src/loader.js b/src/loader.js deleted file mode 100644 index 471dc58..0000000 --- a/src/loader.js +++ /dev/null @@ -1,30 +0,0 @@ -var installer = require("./installer"); -var loaderUtils = require("loader-utils"); -var path = require("path"); -var parser = require("./parser"); -var util = require("util"); - -module.exports = function loader(source, map) { - if (this.cacheable) { - this.cacheable(); - } - - var context = this.options.context || process.cwd(); - var resolve = this.options.resolve; - - var dependencies = parser.parse(source); - - var modulePaths = [].concat( - resolve.root || [], - resolve.modulesDirectories || [] - ).map(function(dir) { - return path.resolve(context, dir); - }); - - var missing = installer.check(dependencies, modulePaths); - var query = loaderUtils.parseQuery(this.query); - - installer.install(missing, query.cli); - - this.callback(null, source, map); -}; diff --git a/test/loader.test.js b/test/loader.test.js deleted file mode 100644 index 84c570b..0000000 --- a/test/loader.test.js +++ /dev/null @@ -1,74 +0,0 @@ -var expect = require("expect"); -var installer = require("../src/installer"); -var loader = require("../src/loader"); -var path = require("path"); - -describe("loader", function() { - beforeEach(function() { - expect.spyOn(console, "info"); - - this.cacheable = function() {}; - this.callback = expect.createSpy(); - this.check = expect.spyOn(installer, "check").andCallThrough(); - this.install = expect.spyOn(installer, "install"); - this.map = "map"; - - this.source = [ - "require('mocha')", - "import expect from 'expect'", - "import * as missing from 'missing'", - ].join("\n"); - - this.options = { - context: process.cwd(), - resolve: { - root: ["test"], - modulesDirectories: ["node_modules"], - }, - }; - - this.query = '?{"cli":{"save":true,"saveExact":true}}'; - - loader.call(this, this.source, this.map); - }); - - afterEach(function() { - expect.restoreSpies(); - }); - - it("should check resolve.root & resolve.modulesDirectories", function() { - expect(this.check).toHaveBeenCalled(); - expect(this.check.calls[0].arguments).toEqual([ - ["mocha", "expect", "missing"], - [ - path.join(this.options.context, "test"), - path.join(this.options.context, "node_modules"), - ], - ]); - }); - - context("when calling .install", function() { - it("should pass dependencies as the first argument", function() { - expect(this.install).toHaveBeenCalled(); - expect(this.install.calls[0].arguments[0]).toEqual(["missing"]); - }); - - it("should pass args as the second argument", function() { - expect(this.install).toHaveBeenCalled(); - expect(this.install.calls[0].arguments[1]).toEqual({ - save: true, - saveExact: true, - }); - }); - }); - - - it("should callback source & map", function() { - expect(this.callback).toHaveBeenCalled(); - expect(this.callback.calls[0].arguments).toEqual([ - null, - this.source, - this.map, - ]); - }); -}); From e1d0308d3b7a79b6577b32e71a2081d5ca34cc81 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:40:02 -0600 Subject: [PATCH 31/52] plugin + test --- src/plugin.js | 55 +++++++----- test/plugin.test.js | 208 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+), 22 deletions(-) create mode 100644 test/plugin.test.js diff --git a/src/plugin.js b/src/plugin.js index d0f794a..0190dc9 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -6,38 +6,49 @@ function NpmInstallPlugin(options) { this.options = options || {}; } -NpmInstallPlugin.prototype.installModule = function(result, next) { +NpmInstallPlugin.prototype.apply = function(compiler) { + // Plugin needs to intercept module resolution before the "official" resolve + compiler.plugin("normal-module-factory", this.listenToFactory); + + // Install loaders on demand + compiler.resolvers.loader.plugin("module", this.resolveLoader.bind(this)); + + // Install project dependencies on demand + compiler.resolvers.normal.plugin("module", this.resolveModule.bind(this)); +}; + +NpmInstallPlugin.prototype.listenToFactory = function(factory) { + factory.plugin("before-resolve", function(result, next) { + // Trigger early-module resolution + factory.resolvers.normal.resolve(result.context, result.request, function(err, filepath) { + next(null, result); + }); + }); +}; + +NpmInstallPlugin.prototype.resolve = function(result) { var dep = installer.check(result.request); if (dep) { installer.install(dep, this.options.cli); } + return dep; +}; + +NpmInstallPlugin.prototype.resolveModule = function(result, next) { + // Only install direct dependencies, not sub-dependencies + if (!result.path.match("node_modules")) { + this.resolve(result); + } + next(); }; -NpmInstallPlugin.prototype.apply = function(compiler) { - // Install loaders on demand - compiler.resolvers.loader.plugin("module", this.installModule.bind(this)); +NpmInstallPlugin.prototype.resolveLoader = function(result, next) { + this.resolve(result); - // Install project dependencies on demand - compiler.resolvers.normal.plugin("module", function(result, next) { - // Skip dependencies of dependencies - if (result.path.match("node_modules")) { - return next(); - } - - this.installModule(result, next); - }.bind(this)); - - compiler.plugin("normal-module-factory", function(factory) { - factory.plugin("before-resolve", function(result, next) { - // Trigger early-module resolution - factory.resolvers.normal.resolve(result.context, result.request, function(err, filepath) { - next(null, result); - }); - }); - }); + next(); }; module.exports = NpmInstallPlugin; diff --git a/test/plugin.test.js b/test/plugin.test.js new file mode 100644 index 0000000..92f69f9 --- /dev/null +++ b/test/plugin.test.js @@ -0,0 +1,208 @@ +var expect = require("expect"); +var installer = require("../src/installer"); +var Plugin = require("../src/plugin"); + +describe("plugin", function() { + beforeEach(function() { + this.options = { + cli: { + save: true, + saveDev: false, + }, + }; + + this.plugin = new Plugin(this.options); + }); + + it("should accept options", function() { + expect(this.plugin.options).toEqual(this.options); + }); + + describe(".apply", function() { + before(function() { + this.compiler = { + plugin: expect.createSpy(), + resolvers: { + loader: { plugin: expect.createSpy() }, + normal: { plugin: expect.createSpy() }, + }, + }; + + this.plugin.apply(this.compiler); + }); + + after(function() { + expect.restoreSpies(); + }); + + it("should hook into `normal-module-factory`", function() { + expect(this.compiler.plugin.calls.length).toBe(1); + expect(this.compiler.plugin.calls[0].arguments).toEqual([ + "normal-module-factory", + this.plugin.listenToFactory + ]); + }); + + it("should hook into loader resolvers", function() { + expect(this.compiler.resolvers.loader.plugin.calls.length).toBe(1); + expect(this.compiler.resolvers.loader.plugin.calls[0].arguments).toEqual([ + "module", + this.plugin.resolveLoader.bind(this.plugin) + ]); + }); + + it("should hook into normal resolvers", function() { + expect(this.compiler.resolvers.normal.plugin.calls.length).toBe(1); + expect(this.compiler.resolvers.normal.plugin.calls[0].arguments).toEqual([ + "module", + this.plugin.resolveModule.bind(this.plugin) + ]); + }); + }); + + describe(".listenToFactory", function() { + before(function() { + this.next = expect.createSpy().andCall(function(err, result) { + return result; + }); + + this.result = { + context: "/", + request: "foo", + path: "node_modules" + }; + + this.factory = { + plugin: expect.createSpy().andCall(function(event, factoryPlugin) { + factoryPlugin(this.result, this.next); + }.bind(this)), + + resolvers: { + normal: { + resolve: expect.createSpy().andCall(function(context, request, callback) { + callback(null, [context, request].join("/")); + }.bind(this)), + }, + }, + }; + + this.plugin.listenToFactory(this.factory); + }); + + it("should hook into `before-resolve`", function() { + expect(this.factory.plugin.calls.length).toBe(1); + expect(this.factory.plugin.calls[0].arguments[0]).toBe("before-resolve"); + }); + + it("should immediately resolve", function() { + expect(this.factory.resolvers.normal.resolve.calls.length).toBe(1); + }); + + it("should pass result through", function() { + expect(this.next.calls.length).toBe(1); + expect(this.next.calls[0].arguments).toEqual([null, this.result]); + }); + }); + + describe(".resolve", function() { + beforeEach(function() { + this.check = expect.spyOn(installer, "check"); + this.install = expect.spyOn(installer, "install"); + this.result = { request: "foo "}; + }); + + afterEach(function() { + this.check.restore(); + this.install.restore(); + }); + + it("should check if request is installed", function() { + this.plugin.resolve(this.result); + + expect(this.check.calls.length).toBe(1); + expect(this.check.calls[0].arguments).toEqual([this.result.request]); + }); + + it("should return the value of the check", function() { + this.check.andReturn(this.result.request); + + var result = this.plugin.resolve(this.result); + + expect(this.check.calls.length).toBe(1); + expect(this.check.calls[0].arguments).toEqual([this.result.request]); + + expect(result).toEqual(this.result.request); + }); + + it("should not install if it exists", function() { + this.plugin.resolve(this.result); + + expect(this.install.calls.length).toEqual(0); + }); + + it("should install if missing", function() { + this.check.andReturn(this.result.request); + this.plugin.resolve(this.result); + + expect(this.install.calls.length).toBe(1); + expect(this.install.calls[0].arguments).toEqual([ + this.result.request, + this.options.cli + ]); + }); + }); + + describe(".resolveModule", function() { + beforeEach(function() { + this.resolve = expect.spyOn(this.plugin, "resolve"); + this.next = expect.createSpy(); + }); + + afterEach(function() { + this.resolve.restore(); + this.next.restore(); + }); + + it("should call .resolve if direct dependency", function() { + var result = { path: "/", request: "foo" }; + + this.plugin.resolveModule(result, this.next); + + expect(this.resolve.calls.length).toBe(1); + expect(this.resolve.calls[0].arguments).toEqual([result]); + expect(this.next.calls.length).toBe(1); + expect(this.next.calls[0].arguments).toEqual([]); + }); + + it("should call not .resolve if sub-dependency", function() { + var result = { path: "node_modules", request: "foo" }; + + this.plugin.resolveModule(result, this.next); + + expect(this.resolve.calls.length).toBe(0); + expect(this.next.calls.length).toBe(1); + expect(this.next.calls[0].arguments).toEqual([]); + }); + }); + + describe(".resolveLoader", function() { + beforeEach(function() { + this.resolve = expect.spyOn(this.plugin, "resolve"); + this.next = expect.createSpy(); + }); + + afterEach(function() { + this.resolve.restore(); + this.next.restore(); + }); + + it("should call .resolve", function() { + var result = { path: "node_modules", request: "foo" }; + + this.plugin.resolveLoader(result, this.next); + + expect(this.resolve.calls.length).toBe(1); + expect(this.resolve.calls[0].arguments).toEqual([result]); + }); + }); +}); From c1d1d0a77075921fb12c08c2e029b463019ba369 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:40:24 -0600 Subject: [PATCH 32/52] Add NPM keyword "webpack-plugin" --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b7ad4f..586ad74 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "keywords": [ "webpack", - "webpack-loader", + "webpack-plugin", "npm", "install" ], From 43acb061c065202c9124f1a2dd60898077135dd0 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:41:16 -0600 Subject: [PATCH 33/52] Fix example/package.json --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index 8239457..a1b0214 100644 --- a/example/package.json +++ b/example/package.json @@ -14,6 +14,6 @@ "start": "webpack --config webpack.config.server.babel.js --watch" }, "dependencies": { - "react": "0.14.6", + "react": "0.14.6" } } From fbfbece40448d40b41934f25368914d7718238e4 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:41:45 -0600 Subject: [PATCH 34/52] Remove babel-core as a dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 586ad74..53b431c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ }, "homepage": "https://github.com/ericclemmons/webpack-npm-install-loader#readme", "dependencies": { - "babel-core": "^6.3.26", "cross-spawn": "^2.1.4", "loader-utils": "^0.2.12", "lodash.kebabcase": "^3.0.1" From 9c1352b4752b64b245c6b8149ac11628747fa5cc Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:41:56 -0600 Subject: [PATCH 35/52] Remove loader-utils as a dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 53b431c..501787b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "homepage": "https://github.com/ericclemmons/webpack-npm-install-loader#readme", "dependencies": { "cross-spawn": "^2.1.4", - "loader-utils": "^0.2.12", "lodash.kebabcase": "^3.0.1" }, "devDependencies": { From 34f662d74b51f3bff2a6e0586e8d8ea2c9236b3c Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sat, 23 Jan 2016 23:42:04 -0600 Subject: [PATCH 36/52] Update dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 501787b..52a7746 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "homepage": "https://github.com/ericclemmons/webpack-npm-install-loader#readme", "dependencies": { "cross-spawn": "^2.1.4", - "lodash.kebabcase": "^3.0.1" + "lodash.kebabcase": "^3.1.0" }, "devDependencies": { "coveralls": "^2.11.6", "expect": "^1.13.4", "mocha": "^2.3.4", - "nyc": "^5.0.1" + "nyc": "^5.3.0" } } From 4dfc30957fdb29784644bad82646a6ede389244b Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:07:26 -0600 Subject: [PATCH 37/52] Remove unnecessary console.error --- src/installer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/installer.js b/src/installer.js index 6ef04f4..e5c87c4 100644 --- a/src/installer.js +++ b/src/installer.js @@ -23,7 +23,6 @@ module.exports.check = function(request) { // Remove cached copy for future checks delete require.cache[pkgPath]; } catch(e) { - console.error(e); throw e; } From b14b1c14e4773b5d7bdbf5c06857f90d2fea091f Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:07:36 -0600 Subject: [PATCH 38/52] Return early for missing dependency --- src/installer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/installer.js b/src/installer.js index e5c87c4..19022bf 100644 --- a/src/installer.js +++ b/src/installer.js @@ -59,6 +59,10 @@ module.exports.check = function(request) { } module.exports.install = function install(dep, options) { + if (!dep) { + return; + } + var args = ["install"].concat([dep]).filter(Boolean); if (options) { From 192468825437cdb5039633be2290a073fb8a18c6 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:07:48 -0600 Subject: [PATCH 39/52] Update installer.test for single-dependency installs --- test/installer.test.js | 101 +++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/test/installer.test.js b/test/installer.test.js index 93cff9e..c7daf32 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -1,45 +1,82 @@ -var spawn = require("cross-spawn"); var expect = require("expect"); +var fs = require("fs"); +var spawn = require("cross-spawn"); + var installer = require("../src/installer"); describe("installer", function() { describe(".check", function() { - context("given resolveable dependencies", function() { - it("should return []", function() { - expect(installer.check(["expect", "mocha"])).toEqual([]); + context("given a local module", function() { + it("should return undefined", function() { + expect(installer.check("./foo")).toBe(undefined); }); }); - context("given relative paths", function() { - it("should return []", function() { - expect(installer.check(["./something"])).toEqual([]); + context("when process.cwd() is missing package.json", function() { + before(function() { + this.cwd = process.cwd(); + + process.chdir(__dirname); + }); + + after(function() { + process.chdir(this.cwd); + }); + + it("should throw", function() { + expect(function() { + installer.check("anything"); + }).toThrow(/Cannot find module/); }); }); - context("given un-resolveable dependencies", function() { - it("should return them", function() { - expect(installer.check(["does-not-exist"])).toEqual(["does-not-exist"]); + context("given a dependency in package.json", function() { + it("should return undefined", function() { + expect(installer.check("cross-spawn")).toBe(undefined); }); + }); - context("that import deeply", function() { - it("should return their module name", function() { - expect(installer.check(["does-not-exist/lib/test"])).toEqual(["does-not-exist"]); - }); + context("given a devDependency in package.json", function() { + it("should return undefined", function() { + expect(installer.check("expect")).toBe(undefined); }); + }); - context("that import multiple times from the same module", function() { - it("should return their module name once", function() { - expect(installer.check(["d-n-e/a", "d-n-e/b"])).toEqual(["d-n-e"]); + context("given a linked dependency", function() { + beforeEach(function() { + this.lstatSync = expect.spyOn(fs, "lstatSync").andReturn({ + isSymbolicLink: function() { + return true; + }, }); }); - context("that exists in alternative directories", function() { - it("should return []", function() { - expect(installer.check(["test"], [process.cwd()])).toEqual([]); - }); + afterEach(function() { + this.lstatSync.restore(); + }); + + it("should return undefined", function() { + expect(installer.check("something-linked")).toBe(undefined); + expect(this.lstatSync.calls.length).toBe(1); + expect(this.lstatSync.calls[0].arguments).toEqual([ + [process.cwd(), "node_modules", "something-linked"].join("/"), + ]); + }); + }); + + context("given a global module", function() { + it("should return undefined", function () { + expect(installer.check("path")).toBe(undefined); }); }); + context("given anything else", function() { + it("should return it", function() { + var dep = "some-module-that-is-not-installed-yet"; + + expect(installer.check(dep)).toBe(dep); + }); + }); }); describe(".install", function() { @@ -53,34 +90,29 @@ describe("installer", function() { expect.restoreSpies(); }); - context("given falsey values", function() { + context("given a falsey value", function() { it("should return undefined", function() { expect(installer.install()).toEqual(undefined); expect(installer.install(0)).toEqual(undefined); + expect(installer.install(false)).toEqual(undefined); + expect(installer.install(null)).toEqual(undefined); expect(installer.install("")).toEqual(undefined); - expect(installer.install([])).toEqual(undefined); - }); - }); - - context("given empty dependencies", function() { - it("should return undefined", function() { - expect(installer.install([])).toEqual(undefined); }); }); - context("given dependencies", function() { - it("should install them", function() { - var result = installer.install(["foo", "bar"]); + context("given a dependency", function() { + it("should install it", function() { + var result = installer.install("foo"); expect(this.spy).toHaveBeenCalled(); expect(this.spy.calls.length).toEqual(1); expect(this.spy.calls[0].arguments[0]).toEqual("npm"); - expect(this.spy.calls[0].arguments[1]).toEqual(["install", "foo", "bar"]); + expect(this.spy.calls[0].arguments[1]).toEqual(["install", "foo"]); }); context("given options", function() { it("should pass them to child process", function() { - var result = installer.install(["foo", "bar"], { + var result = installer.install("foo", { save: true, saveExact: false, registry: "https://registry.npmjs.com/", @@ -92,7 +124,6 @@ describe("installer", function() { expect(this.spy.calls[0].arguments[1]).toEqual([ "install", "foo", - "bar", "--save", "--registry='https://registry.npmjs.com/'", ]); From f206cf3612926acff648e7b5c8f6bdb210c251c9 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:09:52 -0600 Subject: [PATCH 40/52] Remove trailing , --- example/.npmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/.npmrc b/example/.npmrc index 1cf309b..b37ac74 100644 --- a/example/.npmrc +++ b/example/.npmrc @@ -1,3 +1,3 @@ cache-min=9999999 -save=true, +save=true save-exact=true From e299952a5be564b6af3f19b78f245c10978a6cab Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:32:18 -0600 Subject: [PATCH 41/52] Rewrite README for plugin usage --- README.md | 79 ++++++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 72638b3..413ea2b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,21 @@ -# npm-install-loader +# npm-install-webpack-plugin -> Webpack loader to automatically npm install & save dependencies. +> Webpack plugin that automatically **installs & saves missing dependencies** +> while you work! +Seamless works with: +- [x] Javascript + (e.g. `require`, `import`) +- [x] CSS + (e.g. `@import "~bootstrap"`) +- [x] Webpack loaders + (e.g. `babel-loader`, `file-loader`, etc.) -[![travis build](https://img.shields.io/travis/ericclemmons/npm-install-loader.svg)](https://travis-ci.org/ericclemmons/npm-install-loader) -[![Coverage Status](https://coveralls.io/repos/ericclemmons/npm-install-loader/badge.svg?branch=master&service=github)](https://coveralls.io/github/ericclemmons/npm-install-loader?branch=master) -[![version](https://img.shields.io/npm/v/npm-install-loader.svg)](http://npm.im/npm-install-loader) -[![downloads](https://img.shields.io/npm/dm/npm-install-loader.svg)](http://npm-stat.com/charts.html?package=npm-install-loader) -[![MIT License](https://img.shields.io/npm/l/npm-install-loader.svg)](http://opensource.org/licenses/MIT) +[![travis build](https://img.shields.io/travis/ericclemmons/npm-install-webpack-plugin.svg)](https://travis-ci.org/ericclemmons/npm-install-webpack-plugin) +[![Coverage Status](https://coveralls.io/repos/ericclemmons/npm-install-webpack-plugin/badge.svg?branch=master&service=github)](https://coveralls.io/github/ericclemmons/npm-install-webpack-plugin?branch=master) +[![version](https://img.shields.io/npm/v/npm-install-webpack-plugin.svg)](http://npm.im/npm-install-webpack-plugin) +[![downloads](https://img.shields.io/npm/dm/npm-install-webpack-plugin.svg)](http://npm-stat.com/charts.html?package=npm-install-webpack-plugin) +[![MIT License](https://img.shields.io/npm/l/npm-install-webpack-plugin.svg)](http://opensource.org/licenses/MIT) - - - @@ -18,58 +26,41 @@ build script & server just to install a dependency you didn't know you needed until now. Instead, use `require` or `import` how you normally would and `npm install` -will happen automatically install missing dependencies between reloads. +will happen **automatically install & save missing dependencies** while you work! ### Usage In your `webpack.config.js`: ```js -module: { - postLoaders: [ - { - exclude: /node_modules/, - loader: "npm-install-loader", - test: /\.js$/, - }, - ], -} +plugins: [ + new NpmInstallPlugin(), +], ``` -This will ensure that any other loaders -(e.g. `eslint-loader`, `babel-loader`, etc.) have completed. - -### Saving - -This loader simply runs `npm install [modules]`. - -I recommend creating an `.npmrc` file -in the root of your project with: +**If you have an `.npmrc` file in your project, +those arguments will be used:** -```ini +``` save=true +save-exact=true ``` -This will automatically save any dependencies anytime you run `npm install` (no need to pass `--save`). - -**Alternatively**, you can provide CLI arguments that get added directly to `npm install`: +Alternatively, you can provide your own arguments to `npm install`: ```js -postLoaders: [ - { - exclude: /node_modules/, - loader: "npm-install-loader", - query: { - cli: { - registry: "..." // --registry='...' - save: true, // --save - saveExact: true, // --save-exact - }, - }, - test: /\.js$/, - }, +plugins: [ + new NpmInstallPlugin({ + ... + cacheMin: 999999 // --cache-min=999999 (prefer NPM cached version) + registry: "..." // --registry="..." + save: true, // --save + saveDev: true, // --save-dev + saveExact: true, // --save-exact + ... + }), ], - +``` ### License From 922eeb6def4602a309d27b561e48d69250f44b3f Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 00:32:26 -0600 Subject: [PATCH 42/52] Update package.json with plugin name, not loader --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 52a7746..0cc26d5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/ericclemmons/webpack-npm-install-loader.git" + "url": "git+https://github.com/ericclemmons/npm-install-webpack-plugin.git" }, "keywords": [ "webpack", @@ -29,9 +29,9 @@ "author": "Eric Clemmons ", "license": "MIT", "bugs": { - "url": "https://github.com/ericclemmons/webpack-npm-install-loader/issues" + "url": "https://github.com/ericclemmons/npm-install-webpack-plugin/issues" }, - "homepage": "https://github.com/ericclemmons/webpack-npm-install-loader#readme", + "homepage": "https://github.com/ericclemmons/npm-install-webpack-plugin#readme", "dependencies": { "cross-spawn": "^2.1.4", "lodash.kebabcase": "^3.1.0" From b0983760716bd680e6ac82581080bd817474f14f Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:18:44 -0600 Subject: [PATCH 43/52] Add support for @namespaced/modules --- src/installer.js | 7 ++++--- test/installer.test.js | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/installer.js b/src/installer.js index 19022bf..f0047ce 100644 --- a/src/installer.js +++ b/src/installer.js @@ -7,9 +7,10 @@ var util = require("util"); var EXTERNAL = /^[a-z\-0-9]+$/; module.exports.check = function(request) { - // Only look for the dependency directory - // @TODO Support namespaced NPM modules (e.g. @cycle/dom) - var dep = request.split("/").shift().toLowerCase(); + var dep = request.split("/") + .slice(0, request.charAt(0) === "@" ? 2 : 1) + .join("/") + ; // Ignore relative modules, which aren't installed by NPM if (!dep.match(EXTERNAL)) { diff --git a/test/installer.test.js b/test/installer.test.js index c7daf32..43e4b05 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -70,9 +70,23 @@ describe("installer", function() { }); }); - context("given anything else", function() { - it("should return it", function() { - var dep = "some-module-that-is-not-installed-yet"; + context("given a module", function() { + it("should return module", function() { + expect(installer.check("react")).toBe("react"); + }); + }); + + context("given a module/and/path", function() { + it("should return module", function() { + expect(installer.check("react/proptypes")).toBe("react"); + }); + }); + + context("given a @namespaced/module", function() { + it("should return @namespaced/module", function() { + expect(installer.check("@namespaced/module")).toBe("@namespaced/module"); + }); + }); expect(installer.check(dep)).toBe(dep); }); From ae360a9a11610aab05434b79cde61690723e57cd Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:19:02 -0600 Subject: [PATCH 44/52] Simplify INTERNAL vs. EXTERNAL check --- src/installer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/installer.js b/src/installer.js index f0047ce..9300502 100644 --- a/src/installer.js +++ b/src/installer.js @@ -4,7 +4,8 @@ var kebabCase = require("lodash.kebabcase"); var path = require("path"); var util = require("util"); -var EXTERNAL = /^[a-z\-0-9]+$/; +var INTERNAL = /^\./; // Match "./client", "../something", etc. +var EXTERNAL = /^[a-z\-0-9]+$/; // Match "react", "path", "fs", etc. module.exports.check = function(request) { var dep = request.split("/") @@ -13,7 +14,7 @@ module.exports.check = function(request) { ; // Ignore relative modules, which aren't installed by NPM - if (!dep.match(EXTERNAL)) { + if (dep.match(INTERNAL)) { return; } From 1948013810cabc4d40a4815c4b9c61a8f9557de7 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:19:13 -0600 Subject: [PATCH 45/52] Comment on why global module logic works --- src/installer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/installer.js b/src/installer.js index 9300502..07dc376 100644 --- a/src/installer.js +++ b/src/installer.js @@ -47,9 +47,11 @@ module.exports.check = function(request) { // Module exists in node_modules, but isn't symlinked } + // Ignore NPM global modules (e.g. "path", "fs", etc.) try { var resolved = require.resolve(dep); + // Global modules resolve to their name, not an actual path if (resolved.match(EXTERNAL)) { return; } From 343c8e3a65e1d285a28e7f87d21f44e20c4fb100 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:19:25 -0600 Subject: [PATCH 46/52] Test for non-saved dependencies --- test/installer.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/installer.test.js b/test/installer.test.js index 43e4b05..6e9b681 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -88,7 +88,9 @@ describe("installer", function() { }); }); - expect(installer.check(dep)).toBe(dep); + context("given a module already installed, but not saved", function() { + it("should return module", function() { + expect(installer.check("yargs")).toBe("yargs"); }); }); }); From 570cd114fc513a50eefd25d0a865c7e810a486c2 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:19:38 -0600 Subject: [PATCH 47/52] Rename this.spy to this.sync for clarity --- test/installer.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/installer.test.js b/test/installer.test.js index 6e9b681..ff9a074 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -97,7 +97,7 @@ describe("installer", function() { describe(".install", function() { beforeEach(function() { - this.spy = expect.spyOn(spawn, "sync"); + this.sync = expect.spyOn(spawn, "sync"); expect.spyOn(console, "info"); }); @@ -120,10 +120,10 @@ describe("installer", function() { it("should install it", function() { var result = installer.install("foo"); - expect(this.spy).toHaveBeenCalled(); - expect(this.spy.calls.length).toEqual(1); - expect(this.spy.calls[0].arguments[0]).toEqual("npm"); - expect(this.spy.calls[0].arguments[1]).toEqual(["install", "foo"]); + expect(this.sync).toHaveBeenCalled(); + expect(this.sync.calls.length).toEqual(1); + expect(this.sync.calls[0].arguments[0]).toEqual("npm"); + expect(this.sync.calls[0].arguments[1]).toEqual(["install", "foo"]); }); context("given options", function() { @@ -134,10 +134,10 @@ describe("installer", function() { registry: "https://registry.npmjs.com/", }); - expect(this.spy).toHaveBeenCalled(); - expect(this.spy.calls.length).toEqual(1); - expect(this.spy.calls[0].arguments[0]).toEqual("npm"); - expect(this.spy.calls[0].arguments[1]).toEqual([ + expect(this.sync).toHaveBeenCalled(); + expect(this.sync.calls.length).toEqual(1); + expect(this.sync.calls[0].arguments[0]).toEqual("npm"); + expect(this.sync.calls[0].arguments[1]).toEqual([ "install", "foo", "--save", From f61cb584c38d7cbc13713c630514e56dc5862728 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:49:40 -0600 Subject: [PATCH 48/52] Add test for webpack-specific requests --- test/installer.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/installer.test.js b/test/installer.test.js index ff9a074..aab87c9 100644 --- a/test/installer.test.js +++ b/test/installer.test.js @@ -93,6 +93,12 @@ describe("installer", function() { expect(installer.check("yargs")).toBe("yargs"); }); }); + + context("given a webpack !!loader/module", function() { + it("should return undefined", function() { + expect(installer.check("!!./css-loader/index.js',")).toBe(undefined); + }); + }) }); describe(".install", function() { From 3865a5fddc640c11d046d82b8d8c462ada7b0206 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 11:49:52 -0600 Subject: [PATCH 49/52] Fix bug with webpack requests --- src/installer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/installer.js b/src/installer.js index 07dc376..4a5dd11 100644 --- a/src/installer.js +++ b/src/installer.js @@ -8,13 +8,14 @@ var INTERNAL = /^\./; // Match "./client", "../something", etc. var EXTERNAL = /^[a-z\-0-9]+$/; // Match "react", "path", "fs", etc. module.exports.check = function(request) { + var namespaced = request.charAt(0) === "@"; var dep = request.split("/") - .slice(0, request.charAt(0) === "@" ? 2 : 1) + .slice(0, namespaced ? 2 : 1) .join("/") ; // Ignore relative modules, which aren't installed by NPM - if (dep.match(INTERNAL)) { + if (!dep.match(EXTERNAL) && !namespaced) { return; } From d302c39d8be1a63a742b72081f9e1e7f0e757168 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 12:16:40 -0600 Subject: [PATCH 50/52] "missing dependency" is redundant --- src/installer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/installer.js b/src/installer.js index 4a5dd11..e9011f0 100644 --- a/src/installer.js +++ b/src/installer.js @@ -87,7 +87,7 @@ module.exports.install = function install(dep, options) { } } - console.info("Installing missing dependency `%s`...", dep); + console.info("Installing `%s`...", dep); var output = spawn.sync("npm", args, { stdio: "inherit" }); From ffd0537bbd873cbc52a308cd189c3dbe6f86629a Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 12:16:52 -0600 Subject: [PATCH 51/52] index.html can be inlined --- example/src/public/index.html | 8 -------- example/src/server.js | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) delete mode 100644 example/src/public/index.html diff --git a/example/src/public/index.html b/example/src/public/index.html deleted file mode 100644 index 5ab23d3..0000000 --- a/example/src/public/index.html +++ /dev/null @@ -1,8 +0,0 @@ - - -
- Waiting on client.js to load... -
- - - diff --git a/example/src/server.js b/example/src/server.js index 9dfc6fc..e027278 100644 --- a/example/src/server.js +++ b/example/src/server.js @@ -3,24 +3,28 @@ import webpack from "webpack"; import client from "../webpack.config.client.babel"; -const app = express(); const compiler = webpack(client); -app - .use(express.static("build/client")) - .use(express.static("src/public")) +export default express() + .get("/", (req, res) => res.send(` + +
+ Waiting on client.js to execute... +
+ + + `)) .use(require("webpack-dev-middleware")(compiler, { noInfo: true, publicPath: client.output.publicPath, quiet: false, })) - .use(require("webpack-hot-middleware")(compiler, { reload: true })) + .use(require("webpack-hot-middleware")(compiler)) .listen(3000, (err) => { if (err) { - console.error(err); - return; + return console.error(err); } - console.log("Listening on http://localhost:3000/"); + console.info("Listening on http://localhost:3000/") }) ; From e8396b65036cd6748f3c137994216ba8d7006cc3 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Sun, 24 Jan 2016 12:28:38 -0600 Subject: [PATCH 52/52] 2016 --- LICENSE | 3 +-- README.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 87cdf8b..96d744b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Eric Clemmons +Copyright (c) 2016 Eric Clemmons Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 413ea2b..6404ab8 100644 --- a/README.md +++ b/README.md @@ -64,4 +64,4 @@ plugins: [ ### License -> MIT License 2015 © Eric Clemmons +> MIT License 2016 © Eric Clemmons