From a103ec0256455e45b1f4f5c5d36e4790f4e7ef18 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sat, 9 Feb 2019 12:34:09 +0530 Subject: [PATCH 1/9] ui(init): start writing endpoint --- packages/ui-server/.gitignore | 1 + packages/ui-server/routes/api/_entry.js | 70 +++++++++++++++++++++++++ packages/ui-server/routes/api/init.js | 16 +++++- packages/ui-server/utils/questioner.js | 43 +++++++++++++++ packages/ui-server/utils/user.js | 52 ++++++++++++++++++ 5 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 packages/ui-server/.gitignore create mode 100644 packages/ui-server/routes/api/_entry.js create mode 100644 packages/ui-server/utils/questioner.js create mode 100644 packages/ui-server/utils/user.js diff --git a/packages/ui-server/.gitignore b/packages/ui-server/.gitignore new file mode 100644 index 00000000000..93a410214a1 --- /dev/null +++ b/packages/ui-server/.gitignore @@ -0,0 +1 @@ +/utils/**.js diff --git a/packages/ui-server/routes/api/_entry.js b/packages/ui-server/routes/api/_entry.js new file mode 100644 index 00000000000..28eb3ace9f7 --- /dev/null +++ b/packages/ui-server/routes/api/_entry.js @@ -0,0 +1,70 @@ +const webpackEntryPoint = {}; +function forEachPromise(entries, fn) { + return entries.reduce((promise, prop) => { + const trimmedProp = prop.trim(); + + return promise.then((n) => { + if (n) { + Object.keys(n).forEach((val) => { + if ( + n[val].charAt(0) !== "(" && + n[val].charAt(0) !== "[" && + !n[val].includes("function") && + !n[val].includes("path") && + !n[val].includes("process") + ) { + n[val] = `\'${n[val].replace(/"|'/g, "").concat(".js")}\'`; + } + webpackEntryPoint[val] = n[val]; + }); + } else { + n = {}; + } + return fn(trimmedProp); + }); + }, Promise.resolve()); +} + +module.exports = function(questioner, answer) { + if (answer === true) { + return questioner.question({ + action: "question", + question: "Type the names you want for your entry files", + type: "array" + }).then((entries) => { + return forEachPromise(entries, (entryProp) => { + return questioner.question({ + action: "question", + question: `What is the location of "${entryProp}"? [example: ./src/${entryProp}]` + }); + }).then((entryPropAnswer) => { + Object.keys(entryPropAnswer).forEach(val => { + if ( + entryPropAnswer[val].charAt(0) !== "(" && + entryPropAnswer[val].charAt(0) !== "[" && + !entryPropAnswer[val].includes("function") && + !entryPropAnswer[val].includes("path") && + !entryPropAnswer[val].includes("process") + ) { + entryPropAnswer[val] = `\'${entryPropAnswer[val] + .replace(/"|'/g, "") + .concat(".js")}\'`; + } + webpackEntryPoint[val] = entryPropAnswer[val]; + }); + return webpackEntryPoint; + }); + }); + } else { + return questioner.question({ + action: "question", + question: "Which module will be the first to enter the application?" + }).then((singularEntryAnswer) => { + let { singularEntry } = singularEntryAnswer; + if (singularEntry.indexOf("\"") >= 0) { + singularEntry = singularEntry.replace(/"/g, "'"); + } + return singularEntry; + }); + } +}; diff --git a/packages/ui-server/routes/api/init.js b/packages/ui-server/routes/api/init.js index 7f89a4d0585..f79e9772e05 100644 --- a/packages/ui-server/routes/api/init.js +++ b/packages/ui-server/routes/api/init.js @@ -10,7 +10,7 @@ router.get("/", function(req, res, next) { const Config = require("../../../init/config").Config; const scaffoldProject = require("../../../init").scaffoldProject; const Questioner = require("../../utils/questioner").default; - + const entryQuestions = require("./_entry.js"); let config = new Config(); let questioner = new Questioner(); @@ -33,7 +33,19 @@ router.get("/", function(req, res, next) { questioner.start({ action: "question", - question: "A dummy question for reference" + question: "Will your application have multiple bundles?" + }).then((answer) => { + return entryQuestions(questioner, answer); + }).then((entryOptions) => { + if (entryOptions !== "\"\"") { + config.setWebpackOption("webpackOptions", { + entry: entryOptions, + }); + } + return questioner.question({ + action: "question", + question: "Which folder will Which folder will your generated bundles be in? [default: dist]" + }); }).then((data) => { scaffoldProject(dependencies,config.exportConfig()); return questioner.question({ action: "exit" }); diff --git a/packages/ui-server/utils/questioner.js b/packages/ui-server/utils/questioner.js new file mode 100644 index 00000000000..26c9095f486 --- /dev/null +++ b/packages/ui-server/utils/questioner.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const net = require("net"); +class Questioner { + constructor() { + this.port = 1234; + this.address = "localhost"; + } + start(ques) { + return new Promise((resolve, reject) => { + // Create Server + this.server = net.createServer((socket) => { + process.stdout.write(`Client Connected on ${socket.remotePort}\n`); + this.client = socket; + this.client.write(JSON.stringify(ques)); + this.client.on("data", (data) => { + resolve(JSON.parse(data).answer); + }); + this.client.on("close", () => { + reject(); + }); + }).listen(this.port, this.address); + this.server.maxConnections = 1; + }); + } + question(ques) { + return new Promise((resolve, reject) => { + if (ques.action === "exit") { + this.client.destroy(); + this.server.close(); + resolve(); + } + this.client.write(JSON.stringify(ques)); + this.client.on("data", (data) => { + resolve(JSON.parse(data).answer); + }); + this.client.on("close", (err) => { + reject(err); + }); + }); + } +} +exports.default = Questioner; diff --git a/packages/ui-server/utils/user.js b/packages/ui-server/utils/user.js new file mode 100644 index 00000000000..c632a20b44b --- /dev/null +++ b/packages/ui-server/utils/user.js @@ -0,0 +1,52 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const net_1 = require("net"); +class User { + constructor(port, address) { + this.socket = new net_1.Socket(); + this.port = port; + this.address = address; + this.init(); + } + answer(ans) { + const user = this.socket; + user.write(JSON.stringify(ans)); + } + question() { + return __awaiter(this, void 0, void 0, function* () { + const ques = yield this.getQuestion().catch((err) => { + process.stderr.write(err); + }); + return ques; + }); + } + getQuestion() { + return __awaiter(this, void 0, void 0, function* () { + return yield new Promise((resolve, reject) => { + const user = this.socket; + if (user.destroyed) { + resolve(); + } + user.on("data", (data) => { + resolve(JSON.parse(data).question); + }); + user.on("error", (err) => { + reject(err); + }); + }); + }); + } + init() { + const user = this.socket; + user.connect(this.port, this.address); + } +} +exports.default = User; From 4e411e48857af24d593a240526cb4a95c0ddd557 Mon Sep 17 00:00:00 2001 From: rishabh3112 Date: Sat, 9 Feb 2019 01:33:46 -0800 Subject: [PATCH 2/9] ui(init): minor correction --- packages/ui-server/routes/api/_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-server/routes/api/_entry.js b/packages/ui-server/routes/api/_entry.js index 28eb3ace9f7..b06fe4c6226 100644 --- a/packages/ui-server/routes/api/_entry.js +++ b/packages/ui-server/routes/api/_entry.js @@ -60,7 +60,7 @@ module.exports = function(questioner, answer) { action: "question", question: "Which module will be the first to enter the application?" }).then((singularEntryAnswer) => { - let { singularEntry } = singularEntryAnswer; + let singularEntry = singularEntryAnswer; if (singularEntry.indexOf("\"") >= 0) { singularEntry = singularEntry.replace(/"/g, "'"); } From 6253316d6d5975438e97fcfdd6b85b37a0a4eb24 Mon Sep 17 00:00:00 2001 From: rishabh3112 Date: Sun, 10 Feb 2019 08:45:14 -0800 Subject: [PATCH 3/9] ui(init): incorporate inquirer questions --- packages/ui-server/routes/api/_entry.js | 25 +++++++++--- packages/ui-server/routes/api/init.js | 16 +++++--- packages/ui-server/utils/questioner.js | 43 -------------------- packages/ui-server/utils/user.js | 52 ------------------------- 4 files changed, 31 insertions(+), 105 deletions(-) delete mode 100644 packages/ui-server/utils/questioner.js delete mode 100644 packages/ui-server/utils/user.js diff --git a/packages/ui-server/routes/api/_entry.js b/packages/ui-server/routes/api/_entry.js index b06fe4c6226..dc083bd5c65 100644 --- a/packages/ui-server/routes/api/_entry.js +++ b/packages/ui-server/routes/api/_entry.js @@ -1,3 +1,5 @@ +/* eslint-disable node/no-unpublished-require */ +const InputValidate = require("../../../webpack-scaffold").InputValidate; const webpackEntryPoint = {}; function forEachPromise(entries, fn) { return entries.reduce((promise, prop) => { @@ -24,18 +26,28 @@ function forEachPromise(entries, fn) { }); }, Promise.resolve()); } - +/** + * Asks for entry points, either if it has multiple or one entry + * @param {Object} questioner Instance of TCP questioner used by init endpoint + * @param {Object} answer Boolean answer to whether mutiple entries are used or not + * @returns {Object} An Object to entry for webpack config + */ module.exports = function(questioner, answer) { if (answer === true) { return questioner.question({ action: "question", - question: "Type the names you want for your entry files", - type: "array" + question: InputValidate( + "multipleEntries", + "Type the names you want for your modules (entry files), separated by comma [example: app,vendor]" + ), }).then((entries) => { return forEachPromise(entries, (entryProp) => { return questioner.question({ action: "question", - question: `What is the location of "${entryProp}"? [example: ./src/${entryProp}]` + question: InputValidate( + `${entryProp}`, + `What is the location of "${entryProp}"? [example: ./src/${entryProp}]` + ), }); }).then((entryPropAnswer) => { Object.keys(entryPropAnswer).forEach(val => { @@ -58,7 +70,10 @@ module.exports = function(questioner, answer) { } else { return questioner.question({ action: "question", - question: "Which module will be the first to enter the application?" + question: InputValidate( + "singularEntry", + "Which module will be the first to enter the application? [default: ./src/index]" + ) }).then((singularEntryAnswer) => { let singularEntry = singularEntryAnswer; if (singularEntry.indexOf("\"") >= 0) { diff --git a/packages/ui-server/routes/api/init.js b/packages/ui-server/routes/api/init.js index f79e9772e05..6204810db04 100644 --- a/packages/ui-server/routes/api/init.js +++ b/packages/ui-server/routes/api/init.js @@ -11,6 +11,11 @@ router.get("/", function(req, res, next) { const scaffoldProject = require("../../../init").scaffoldProject; const Questioner = require("../../utils/questioner").default; const entryQuestions = require("./_entry.js"); + + const Confirm = require("../../../webpack-scaffold").Confirm; + const Input = require("../../../webpack-scaffold").Input; + const List = require("../../../webpack-scaffold").List; + let config = new Config(); let questioner = new Questioner(); @@ -33,18 +38,19 @@ router.get("/", function(req, res, next) { questioner.start({ action: "question", - question: "Will your application have multiple bundles?" + question: Confirm("entryType", "Will your application have multiple bundles?"), }).then((answer) => { return entryQuestions(questioner, answer); }).then((entryOptions) => { if (entryOptions !== "\"\"") { - config.setWebpackOption("webpackOptions", { - entry: entryOptions, - }); + config.setWebpackOption("entry", entryOptions); } return questioner.question({ action: "question", - question: "Which folder will Which folder will your generated bundles be in? [default: dist]" + question: Input( + "outputType", + "Which folder will your generated bundles be in? [default: dist]:" + ), }); }).then((data) => { scaffoldProject(dependencies,config.exportConfig()); diff --git a/packages/ui-server/utils/questioner.js b/packages/ui-server/utils/questioner.js deleted file mode 100644 index 26c9095f486..00000000000 --- a/packages/ui-server/utils/questioner.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const net = require("net"); -class Questioner { - constructor() { - this.port = 1234; - this.address = "localhost"; - } - start(ques) { - return new Promise((resolve, reject) => { - // Create Server - this.server = net.createServer((socket) => { - process.stdout.write(`Client Connected on ${socket.remotePort}\n`); - this.client = socket; - this.client.write(JSON.stringify(ques)); - this.client.on("data", (data) => { - resolve(JSON.parse(data).answer); - }); - this.client.on("close", () => { - reject(); - }); - }).listen(this.port, this.address); - this.server.maxConnections = 1; - }); - } - question(ques) { - return new Promise((resolve, reject) => { - if (ques.action === "exit") { - this.client.destroy(); - this.server.close(); - resolve(); - } - this.client.write(JSON.stringify(ques)); - this.client.on("data", (data) => { - resolve(JSON.parse(data).answer); - }); - this.client.on("close", (err) => { - reject(err); - }); - }); - } -} -exports.default = Questioner; diff --git a/packages/ui-server/utils/user.js b/packages/ui-server/utils/user.js deleted file mode 100644 index c632a20b44b..00000000000 --- a/packages/ui-server/utils/user.js +++ /dev/null @@ -1,52 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const net_1 = require("net"); -class User { - constructor(port, address) { - this.socket = new net_1.Socket(); - this.port = port; - this.address = address; - this.init(); - } - answer(ans) { - const user = this.socket; - user.write(JSON.stringify(ans)); - } - question() { - return __awaiter(this, void 0, void 0, function* () { - const ques = yield this.getQuestion().catch((err) => { - process.stderr.write(err); - }); - return ques; - }); - } - getQuestion() { - return __awaiter(this, void 0, void 0, function* () { - return yield new Promise((resolve, reject) => { - const user = this.socket; - if (user.destroyed) { - resolve(); - } - user.on("data", (data) => { - resolve(JSON.parse(data).question); - }); - user.on("error", (err) => { - reject(err); - }); - }); - }); - } - init() { - const user = this.socket; - user.connect(this.port, this.address); - } -} -exports.default = User; From fd87903d6e0e118355730b76f02e4f7916ffa5e8 Mon Sep 17 00:00:00 2001 From: rishabh3112 Date: Tue, 12 Feb 2019 12:44:00 +0530 Subject: [PATCH 4/9] ui(init): complete port of init generator --- packages/init/config.ts | 2 +- packages/ui-server/routes/api/_entry.js | 2 +- packages/ui-server/routes/api/_module.js | 26 ++ packages/ui-server/routes/api/_plugins.js | 7 + packages/ui-server/routes/api/_tooltip.js | 50 ++++ packages/ui-server/routes/api/init.js | 328 +++++++++++++++++++++- 6 files changed, 407 insertions(+), 8 deletions(-) create mode 100644 packages/ui-server/routes/api/_module.js create mode 100644 packages/ui-server/routes/api/_plugins.js create mode 100644 packages/ui-server/routes/api/_tooltip.js diff --git a/packages/init/config.ts b/packages/init/config.ts index 7c7cf7bb56b..d17235f93fb 100644 --- a/packages/init/config.ts +++ b/packages/init/config.ts @@ -9,7 +9,7 @@ export class Config { // Data members private configName: string = ""; private topScope: string[] = [""]; - private webpackOptions: IWebpackOptions = {}; + public webpackOptions: IWebpackOptions = {}; private merge: object = {}; private configPath: string = process.cwd(); diff --git a/packages/ui-server/routes/api/_entry.js b/packages/ui-server/routes/api/_entry.js index dc083bd5c65..2b678463a17 100644 --- a/packages/ui-server/routes/api/_entry.js +++ b/packages/ui-server/routes/api/_entry.js @@ -33,7 +33,7 @@ function forEachPromise(entries, fn) { * @returns {Object} An Object to entry for webpack config */ module.exports = function(questioner, answer) { - if (answer === true) { + if (answer.entryType === true) { return questioner.question({ action: "question", question: InputValidate( diff --git a/packages/ui-server/routes/api/_module.js b/packages/ui-server/routes/api/_module.js new file mode 100644 index 00000000000..25a8b64f7a0 --- /dev/null +++ b/packages/ui-server/routes/api/_module.js @@ -0,0 +1,26 @@ +/** + * + * Returns an module.rule object that has the babel loader if invoked + * + * @returns {Function} A callable function that adds the babel-loader with env preset + */ +module.exports = function() { + return { + include: ["path.resolve(__dirname, 'src')"], + loader: "'babel-loader'", + options: { + plugins: [ + "'syntax-dynamic-import'", + ], + presets: [ + [ + "'env'", + { + "'modules'": false, + }, + ], + ], + }, + test: `${new RegExp(/\.js$/)}`, + }; +}; diff --git a/packages/ui-server/routes/api/_plugins.js b/packages/ui-server/routes/api/_plugins.js new file mode 100644 index 00000000000..243ce68e8f2 --- /dev/null +++ b/packages/ui-server/routes/api/_plugins.js @@ -0,0 +1,7 @@ +/** + * Callable function with the initial plugins + * + * @returns {Function} An function that returns an array + * that consists of the uglify plugin + */ +module.exports = () => ["new UglifyJSPlugin()"]; diff --git a/packages/ui-server/routes/api/_tooltip.js b/packages/ui-server/routes/api/_tooltip.js new file mode 100644 index 00000000000..c11131ab457 --- /dev/null +++ b/packages/ui-server/routes/api/_tooltip.js @@ -0,0 +1,50 @@ +/** + * + * Tooltip object that consists of tooltips for various of + * features + * + * @returns {Object} An Object that consists of tooltip methods to be invoked + */ +module.exports = { + cssPlugin: () => `/* +* We've enabled MiniCssExtractPlugin for you. This allows your app to +* use css modules that will be moved into a separate CSS file instead of inside +* one of your module entries! +* +* https://github.com/webpack-contrib/mini-css-extract-plugin +* +*/`, + splitChunks: () => `/* +* SplitChunksPlugin is enabled by default and replaced +* deprecated CommonsChunkPlugin. It automatically identifies modules which +* should be splitted of chunk by heuristics using module duplication count and +* module category (i. e. node_modules). And splits the chunks… +* +* It is safe to remove "splitChunks" from the generated configuration +* and was added as an educational example. +* +* https://webpack.js.org/plugins/split-chunks-plugin/ +* +*/`, + postcss: () => `/* +* We've enabled Postcss, autoprefixer and precss for you. This allows your app +* to lint CSS, support variables and mixins, transpile future CSS syntax, +* inline images, and more! +* +* To enable SASS or LESS, add the respective loaders to module.rules +* +* https://github.com/postcss/postcss +* +* https://github.com/postcss/autoprefixer +* +* https://github.com/jonathantneal/precss +* +*/`, + uglify: () => `/* +* We've enabled UglifyJSPlugin for you! This minifies your app +* in order to load faster and run less javascript. +* +* https://github.com/webpack-contrib/uglifyjs-webpack-plugin +* +*/` +}; diff --git a/packages/ui-server/routes/api/init.js b/packages/ui-server/routes/api/init.js index 6204810db04..6206b1cdd89 100644 --- a/packages/ui-server/routes/api/init.js +++ b/packages/ui-server/routes/api/init.js @@ -10,8 +10,10 @@ router.get("/", function(req, res, next) { const Config = require("../../../init/config").Config; const scaffoldProject = require("../../../init").scaffoldProject; const Questioner = require("../../utils/questioner").default; - const entryQuestions = require("./_entry.js"); - + const entryQuestions = require("./_entry"); + const getBabelPlugin = require("./_module"); + const getDefaultPlugins = require("./_plugins"); + const tooltip = require("./_tooltip"); const Confirm = require("../../../webpack-scaffold").Confirm; const Input = require("../../../webpack-scaffold").Input; const List = require("../../../webpack-scaffold").List; @@ -19,7 +21,7 @@ router.get("/", function(req, res, next) { let config = new Config(); let questioner = new Questioner(); - const dependencies = [ + let dependencies = [ "webpack", "webpack-cli", "uglifyjs-webpack-plugin", @@ -36,14 +38,21 @@ router.get("/", function(req, res, next) { rules: [] }); + let usingDefaults = false; + let isProd = false; + let ExtractUseProps = []; + let regExpForStyles; + questioner.start({ action: "question", question: Confirm("entryType", "Will your application have multiple bundles?"), - }).then((answer) => { - return entryQuestions(questioner, answer); + }).then((entryTypeAnswer) => { + return entryQuestions(questioner, entryTypeAnswer); }).then((entryOptions) => { if (entryOptions !== "\"\"") { config.setWebpackOption("entry", entryOptions); + } else { + usingDefaults = true; } return questioner.question({ action: "question", @@ -52,7 +61,314 @@ router.get("/", function(req, res, next) { "Which folder will your generated bundles be in? [default: dist]:" ), }); - }).then((data) => { + }).then((outputTypeAnswer) => { + if ( + !config.webpackOptions.entry && + !usingDefaults + ) { + config.webpackOptions.output = { + chunkFilename: "'[name].[chunkhash].js'", + filename: "'[name].[chunkhash].js'", + }; + } else if (!usingDefaults) { + config.webpackOptions.output = { + filename: "'[name].[chunkhash].js'", + }; + } + + let outputPath = "dist"; + if (outputTypeAnswer.outputType.length) { + outputPath = outputTypeAnswer.outputType; + } + if (!usingDefaults) { + config.webpackOptions.output.path = `path.resolve(__dirname, '${outputPath}')`; + } + }).then((_) => { + isProd = usingDefaults ? true : false; + config.setConfigName(isProd ? "prod" : "dev"); + config.webpackOptions.mode = isProd + ? "'production'" + : "'development'"; + config.webpackOptions.plugins = isProd ? [] : getDefaultPlugins(); + + return questioner.question([ + Confirm("babelConfirm", "Will you be using ES2015?"), + ]); + }).then((babelConfirmAnswer) => { + if (babelConfirmAnswer.babelConfirm === true) { + config.webpackOptions.module.rules.push( + getBabelPlugin() + ); + dependencies.push( + "babel-core", + "babel-loader", + "babel-preset-env" + ); + } + }).then((_) => { + return questioner.question([ + List("stylingType", "Will you use one of the below CSS solutions?", [ + "SASS", + "LESS", + "CSS", + "PostCSS", + "No", + ]), + ]); + }).then((stylingTypeAnswer) => { + switch (stylingTypeAnswer.stylingType) { + case "SASS": + dependencies.push( + "sass-loader", + "node-sass", + "style-loader", + "css-loader" + ); + regExpForStyles = `${new RegExp(/\.(scss|css)$/)}`; + if (isProd) { + ExtractUseProps.push( + { + loader: "'css-loader'", + options: { + sourceMap: true, + }, + }, + { + loader: "'sass-loader'", + options: { + sourceMap: true, + }, + } + ); + } else { + ExtractUseProps.push( + { + loader: "'style-loader'", + }, + { + loader: "'css-loader'", + }, + { + loader: "'sass-loader'", + } + ); + } + break; + case "LESS": + regExpForStyles = `${new RegExp(/\.(less|css)$/)}`; + dependencies.push( + "less-loader", + "less", + "style-loader", + "css-loader" + ); + if (isProd) { + ExtractUseProps.push( + { + loader: "'css-loader'", + options: { + sourceMap: true, + }, + }, + { + loader: "'less-loader'", + options: { + sourceMap: true, + }, + } + ); + } else { + ExtractUseProps.push( + { + loader: "'css-loader'", + options: { + sourceMap: true, + }, + }, + { + loader: "'less-loader'", + options: { + sourceMap: true, + }, + } + ); + } + break; + case "PostCSS": + config.pushToTopScope( + tooltip.postcss(), + "const autoprefixer = require('autoprefixer');", + "const precss = require('precss');", + "\n" + ); + dependencies.push( + "style-loader", + "css-loader", + "postcss-loader", + "precss", + "autoprefixer" + ); + regExpForStyles = `${new RegExp(/\.css$/)}`; + if (isProd) { + ExtractUseProps.push( + { + loader: "'css-loader'", + options: { + importLoaders: 1, + sourceMap: true, + }, + }, + { + loader: "'postcss-loader'", + options: { + plugins: `function () { + return [ + precss, + autoprefixer + ]; + }`, + }, + } + ); + } else { + ExtractUseProps.push( + { + loader: "'style-loader'", + }, + { + loader: "'css-loader'", + options: { + importLoaders: 1, + sourceMap: true, + }, + }, + { + loader: "'postcss-loader'", + options: { + plugins: `function () { + return [ + precss, + autoprefixer + ]; + }`, + }, + } + ); + } + break; + case "CSS": + dependencies.push("style-loader", "css-loader"); + regExpForStyles = `${new RegExp(/\.css$/)}`; + if (isProd) { + ExtractUseProps.push({ + loader: "'css-loader'", + options: { + sourceMap: true, + }, + }); + } else { + ExtractUseProps.push( + { + loader: "'style-loader'", + options: { + sourceMap: true, + }, + }, + { + loader: "'css-loader'", + } + ); + } + break; + default: + regExpForStyles = null; + } + }).then((_) => { + if (isProd) { + // Ask if the user wants to use extractPlugin + return this.prompt([ + Input( + "extractPlugin", + "If you want to bundle your CSS files, what will you name the bundle?" + ), + ]); + } + }).then((extractPluginAnswer) => { + if (regExpForStyles) { + if (this.isProd) { + const cssBundleName = extractPluginAnswer.extractPlugin; + config.pushToTopScope(tooltip.cssPlugin()); + dependencies.push("mini-css-extract-plugin"); + + if (cssBundleName.length !== 0) { + config.webpackOptions.plugins.push( + // TODO: use [contenthash] after it is supported + `new MiniCssExtractPlugin({ filename:'${cssBundleName}.[chunkhash].css' })` + ); + } else { + config.webpackOptions.plugins.push( + "new MiniCssExtractPlugin({ filename:'style.css' })" + ); + } + + ExtractUseProps.unshift({ + loader: "MiniCssExtractPlugin.loader", + }); + + const moduleRulesObj = { + test: regExpForStyles, + use: ExtractUseProps, + }; + + config.webpackOptions.module.rules.push( + moduleRulesObj + ); + config.pushToTopScope( + "const MiniCssExtractPlugin = require('mini-css-extract-plugin');", + "\n" + ); + } else { + const moduleRulesObj = { + test: regExpForStyles, + use: ExtractUseProps, + }; + + config.webpackOptions.module.rules.push( + moduleRulesObj + ); + } + } + // add splitChunks options for transparency + // defaults coming from: https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks + config.pushToTopScope(tooltip.splitChunks()); + config.webpackOptions.optimization = { + splitChunks: { + cacheGroups: { + vendors: { + priority: -10, + test: "/[\\\\/]node_modules[\\\\/]/", + }, + }, + chunks: "'async'", + minChunks: 1, + minSize: 30000, + // for production name is recommended to be off + name: !isProd, + }, + }; + }).then((_) => { + // installPlugins() + if (isProd) { + dependencies = dependencies.filter( + (p) => p !== "uglifyjs-webpack-plugin" + ); + } else { + config.pushToTopScope( + tooltip.uglify(), + "const UglifyJSPlugin = require('uglifyjs-webpack-plugin');", + "\n" + ); + } scaffoldProject(dependencies,config.exportConfig()); return questioner.question({ action: "exit" }); }); From a2b58fe6ab7f5e76db52c80f07041133cc967bdc Mon Sep 17 00:00:00 2001 From: rishabh3112 Date: Sat, 23 Feb 2019 17:55:02 +0530 Subject: [PATCH 5/9] ui: add static fallback --- packages/ui/bin/www.js | 5 ++++- packages/ui/static_fallback/index.html | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 packages/ui/static_fallback/index.html diff --git a/packages/ui/bin/www.js b/packages/ui/bin/www.js index 14e03f9c1d0..5f9f113842a 100644 --- a/packages/ui/bin/www.js +++ b/packages/ui/bin/www.js @@ -4,7 +4,10 @@ */ const path = require("path"); -const app = require("../../ui-server")(path.join(__dirname, "../../ui-gui/build")); // eslint-disable-line +const STATIC = path.existsSync(path.join(__dirname, "../../ui-gui/build")) + ? path.join(__dirname, "../../ui-gui/build") + : "../static_fallback"; +const app = require("../../ui-server")(STATIC); // eslint-disable-line const debug = require("debug")("ui:server"); const http = require("http"); diff --git a/packages/ui/static_fallback/index.html b/packages/ui/static_fallback/index.html new file mode 100644 index 00000000000..0e72f19028c --- /dev/null +++ b/packages/ui/static_fallback/index.html @@ -0,0 +1,12 @@ + + + + + + + webpack-cli UI + + +

The @webpack-cli/ui-gui have not been built.

+ + \ No newline at end of file From 6fc3a2dbe7bff2ad7e705877254491c4e05ab3dc Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Wed, 27 Feb 2019 22:40:54 +0530 Subject: [PATCH 6/9] chore: fix existsSync function --- packages/ui/bin/www.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/bin/www.js b/packages/ui/bin/www.js index 5f9f113842a..e33b85e1a33 100644 --- a/packages/ui/bin/www.js +++ b/packages/ui/bin/www.js @@ -4,7 +4,8 @@ */ const path = require("path"); -const STATIC = path.existsSync(path.join(__dirname, "../../ui-gui/build")) +const fs = require("fs"); +const STATIC = fs.existsSync(path.join(__dirname, "../../ui-gui/build")) ? path.join(__dirname, "../../ui-gui/build") : "../static_fallback"; const app = require("../../ui-server")(STATIC); // eslint-disable-line From 7e340b1f5eb9f1d03e9d3244b0f21d41203fd94c Mon Sep 17 00:00:00 2001 From: rishabh3112 Date: Thu, 7 Mar 2019 20:05:02 +0530 Subject: [PATCH 7/9] ui(docs): update ui-gui guide --- packages/ui-gui/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-gui/README.md b/packages/ui-gui/README.md index b9dfd953296..fad7e591235 100644 --- a/packages/ui-gui/README.md +++ b/packages/ui-gui/README.md @@ -4,9 +4,8 @@ GUI for `webpack-cli ui` #### Package Structure (base: `./`) -1. `./`: Backend will serve from root folder +1. `./build`: contains bundled resources and frontend serves from this folder. 2. `./src` : contains source -3. `./build`: contains bundled resources ## CLI Commands ``` bash @@ -21,3 +20,4 @@ npm run build # test the production build locally npm run serve +``` From 8e5cceb1bbab7005c7160e0dfe4e704310c69080 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Fri, 15 Mar 2019 19:48:37 +0530 Subject: [PATCH 8/9] ui(init): identify user directory --- packages/ui-server/routes/api/_entry.js | 2 +- packages/ui-server/routes/api/init.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui-server/routes/api/_entry.js b/packages/ui-server/routes/api/_entry.js index 2b678463a17..e9f157c40bc 100644 --- a/packages/ui-server/routes/api/_entry.js +++ b/packages/ui-server/routes/api/_entry.js @@ -75,7 +75,7 @@ module.exports = function(questioner, answer) { "Which module will be the first to enter the application? [default: ./src/index]" ) }).then((singularEntryAnswer) => { - let singularEntry = singularEntryAnswer; + let singularEntry = singularEntryAnswer.singularEntry; if (singularEntry.indexOf("\"") >= 0) { singularEntry = singularEntry.replace(/"/g, "'"); } diff --git a/packages/ui-server/routes/api/init.js b/packages/ui-server/routes/api/init.js index 6206b1cdd89..dc1d773d4e0 100644 --- a/packages/ui-server/routes/api/init.js +++ b/packages/ui-server/routes/api/init.js @@ -21,6 +21,8 @@ router.get("/", function(req, res, next) { let config = new Config(); let questioner = new Questioner(); + config.setConfigPath(process.env.CWD); + let dependencies = [ "webpack", "webpack-cli", From 7a601a50605f980f0b8474a28e25d3ea1aca2be9 Mon Sep 17 00:00:00 2001 From: Rishabh Chawla Date: Sun, 17 Mar 2019 18:34:07 +0530 Subject: [PATCH 9/9] ui(init): added some comments --- packages/ui-server/routes/api/init.js | 44 +++++++++++++++++++-------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/ui-server/routes/api/init.js b/packages/ui-server/routes/api/init.js index dc1d773d4e0..9f97a06f6ff 100644 --- a/packages/ui-server/routes/api/init.js +++ b/packages/ui-server/routes/api/init.js @@ -7,13 +7,22 @@ const router = express.Router(); router.get("/", function(req, res, next) { + // Configuration Manager const Config = require("../../../init/config").Config; + + // Scaffolder const scaffoldProject = require("../../../init").scaffoldProject; + + // Questioner const Questioner = require("../../utils/questioner").default; + + // Utilities const entryQuestions = require("./_entry"); const getBabelPlugin = require("./_module"); const getDefaultPlugins = require("./_plugins"); const tooltip = require("./_tooltip"); + + // Question types used const Confirm = require("../../../webpack-scaffold").Confirm; const Input = require("../../../webpack-scaffold").Input; const List = require("../../../webpack-scaffold").List; @@ -94,7 +103,10 @@ router.get("/", function(req, res, next) { config.webpackOptions.plugins = isProd ? [] : getDefaultPlugins(); return questioner.question([ - Confirm("babelConfirm", "Will you be using ES2015?"), + { + action: "question", + question: Confirm("babelConfirm", "Will you be using ES2015?"), + } ]); }).then((babelConfirmAnswer) => { if (babelConfirmAnswer.babelConfirm === true) { @@ -109,13 +121,16 @@ router.get("/", function(req, res, next) { } }).then((_) => { return questioner.question([ - List("stylingType", "Will you use one of the below CSS solutions?", [ - "SASS", - "LESS", - "CSS", - "PostCSS", - "No", - ]), + { + action: "question", + question: List("stylingType", "Will you use one of the below CSS solutions?", [ + "SASS", + "LESS", + "CSS", + "PostCSS", + "No", + ]), + } ]); }).then((stylingTypeAnswer) => { switch (stylingTypeAnswer.stylingType) { @@ -288,11 +303,14 @@ router.get("/", function(req, res, next) { }).then((_) => { if (isProd) { // Ask if the user wants to use extractPlugin - return this.prompt([ - Input( - "extractPlugin", - "If you want to bundle your CSS files, what will you name the bundle?" - ), + return questioner.question([ + { + action: "question", + question: Input( + "extractPlugin", + "If you want to bundle your CSS files, what will you name the bundle?" + ), + } ]); } }).then((extractPluginAnswer) => {