├── .gitignore ├── bin ├── handlers │ ├── sourceFiles │ │ ├── style.tpl │ │ ├── entry.tpl │ │ ├── entry.x.tpl │ │ ├── tasks │ │ │ └── imports.js │ │ └── sourceFilesHandler.js │ ├── views │ │ ├── html.tpl │ │ ├── tasks │ │ │ ├── name.js │ │ │ └── resources.js │ │ └── viewsHandler.js │ ├── index.js │ ├── webpackConfig │ │ ├── tasks │ │ │ ├── plugins.js │ │ │ ├── extensions.js │ │ │ ├── entry.js │ │ │ └── rules.js │ │ ├── webpackConfigHandler.js │ │ └── webpack.config.tpl │ ├── dotFiles │ │ └── dotFilesHandler.js │ ├── tsConfig │ │ └── tsConfigHandler.js │ └── packageJson │ │ └── packageJsonHandler.js ├── params.js ├── index.js ├── utils.js ├── createWpapp.js ├── appGenerator.js └── setup.js ├── package.json ├── LICENCE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | *.log 4 | node_modules 5 | package-lock.json -------------------------------------------------------------------------------- /bin/handlers/sourceFiles/style.tpl: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } -------------------------------------------------------------------------------- /bin/handlers/sourceFiles/entry.tpl: -------------------------------------------------------------------------------- 1 | <@=ENTRY.IMPORTS=@> 2 | 3 | const $root = document.getElementById("root"); 4 | 5 | $root.innerHTML = `

It Works!

`; 6 | -------------------------------------------------------------------------------- /bin/handlers/sourceFiles/entry.x.tpl: -------------------------------------------------------------------------------- 1 | <@=ENTRY.IMPORTS=@> 2 | 3 | const $root = document.getElementById("root"); 4 | 5 | render( 6 |

7 | It Works! 8 |

9 | , $root 10 | ); 11 | -------------------------------------------------------------------------------- /bin/params.js: -------------------------------------------------------------------------------- 1 | const { getGitConfigValue } = require("./utils"); 2 | 3 | module.exports = { 4 | APP_PATH: process.cwd(), 5 | GIT_USER_NAME: getGitConfigValue("user.name"), 6 | GIT_USER_EMAIL: getGitConfigValue("user.email") 7 | } -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const chalk = require("chalk"); 4 | const { isEmptyDir } = require("./utils"); 5 | const { APP_PATH } = require("./params"); 6 | 7 | if (!isEmptyDir(APP_PATH)) { 8 | console.log(chalk.red("Please run on an empty folder!")); 9 | process.exit(1); 10 | } 11 | 12 | require("./createWpapp"); -------------------------------------------------------------------------------- /bin/handlers/views/html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <@=APP.NAME=@> 7 | 8 | <@=APP.RESOURCES=@> 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /bin/handlers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dotFilesHandler: require("./dotFiles/dotFilesHandler"), 3 | packageJsonHandler: require("./packageJson/packageJsonHandler"), 4 | sourceFilesHandler: require("./sourceFiles/sourceFilesHandler"), 5 | viewsHandler: require("./views/viewsHandler"), 6 | webpackConfigHandler: require("./webpackConfig/webpackConfigHandler"), 7 | tsConfigHandler: require("./tsConfig/tsConfigHandler") 8 | } -------------------------------------------------------------------------------- /bin/handlers/views/tasks/name.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=APP.NAME=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | this.push(file.replace(new RegExp(KEY, "g"), params.app_name)); 13 | 14 | cb(); 15 | } 16 | 17 | return stream; 18 | } -------------------------------------------------------------------------------- /bin/handlers/views/tasks/resources.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=APP.RESOURCES=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | const resources = []; 13 | 14 | if (params.use_css) { 15 | resources.push(``); 16 | } 17 | 18 | this.push(file.replace(new RegExp(KEY, "g"), resources.join('\n'))); 19 | 20 | cb(); 21 | } 22 | 23 | return stream; 24 | } -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/tasks/plugins.js: -------------------------------------------------------------------------------- 1 | 2 | const { Transform } = require("stream"); 3 | 4 | module.exports = function (params) { 5 | 6 | const stream = Transform({ objectMode: true }); 7 | const KEY = "<@=PLUGINS=@>"; 8 | 9 | stream._transform = function (file, encoding, cb) { 10 | 11 | file = file.toString(); 12 | 13 | const plugins = []; 14 | 15 | if (params.use_css) { 16 | plugins.push(`new ExtractTextPlugin("bundle.css", { allChunks: true })`); 17 | } 18 | 19 | this.push(file.replace(new RegExp(KEY, "g"), plugins.join(", "))); 20 | 21 | cb(); 22 | } 23 | 24 | return stream; 25 | } -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/tasks/extensions.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=RESOLVE.EXTENSIONS=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | let extensions = [".js"]; 13 | 14 | if (params.type === "typescript") { 15 | extensions.push(".ts"); 16 | 17 | if (params.use_react) { 18 | extensions.push(".tsx"); 19 | } 20 | } 21 | 22 | this.push(file.replace(new RegExp(KEY, "g"), JSON.stringify(extensions))); 23 | 24 | cb(); 25 | } 26 | 27 | return stream; 28 | } -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/tasks/entry.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=ENTRY.FILE=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | let entry; 13 | 14 | if (params.type === "typescript") { 15 | entry = "app.ts"; 16 | 17 | if (params.use_react) { 18 | entry = "app.tsx"; 19 | } 20 | 21 | } else if (params.type === "javascript") { 22 | entry = "app.js"; 23 | } else { 24 | entry = ""; 25 | } 26 | 27 | this.push(file.replace(new RegExp(KEY, "g"), JSON.stringify(entry))); 28 | 29 | cb(); 30 | } 31 | 32 | return stream; 33 | } -------------------------------------------------------------------------------- /bin/handlers/views/viewsHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const fse = require("fs-extra"); 3 | const path = require("path"); 4 | 5 | const name = require("./tasks/name"); 6 | const resources = require("./tasks/resources"); 7 | 8 | module.exports = function (appPath, params) { 9 | 10 | const templatePath = path.resolve(__dirname, "html.tpl"); 11 | const baseDestPath = path.resolve(appPath, "views"); 12 | const destPath = path.resolve(baseDestPath, "index.html"); 13 | 14 | fse.ensureDirSync(baseDestPath); 15 | 16 | return new Promise((resolve, reject) => { 17 | let stream = fs.createReadStream(templatePath) 18 | .pipe(name(params)) 19 | .pipe(resources(params)) 20 | .pipe(fs.createWriteStream(destPath)); 21 | 22 | stream.on("finish", resolve); 23 | stream.on("error", reject); 24 | }); 25 | } -------------------------------------------------------------------------------- /bin/utils.js: -------------------------------------------------------------------------------- 1 | const { spawnSync, execSync } = require("child_process"); 2 | const fs = require("fs"); 3 | 4 | const clearNewLineFromBuffer = exports.clearNewLineFromBuffer = function (buffer) { 5 | return buffer.toString().replace(/\n/g, ""); 6 | } 7 | 8 | const getGitConfigValue = exports.getGitConfigValue = function (info) { 9 | return clearNewLineFromBuffer(spawnSync("git", ["config", info]).stdout); 10 | } 11 | 12 | const getENVParamValue = exports.getENVParamValue = function (param) { 13 | return clearNewLineFromBuffer(execSync(`echo $${param}`)); 14 | } 15 | 16 | const isEmptyDir = exports.isEmptyDir = function (path) { 17 | return fs.existsSync(path) && fs.readdirSync(path).length === 0; 18 | } 19 | 20 | const isValidHTTPPort = exports.isValidHTTPPort = function (port) { 21 | const parsed = +port; 22 | return parsed >= 1 && parsed <= 65535 && port === parsed.toString(); 23 | } -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/webpackConfigHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const entry = require("./tasks/entry"); 5 | const extensions = require("./tasks/extensions"); 6 | const rules = require("./tasks/rules"); 7 | const plugins = require("./tasks/plugins"); 8 | 9 | module.exports = function (appPath, params) { 10 | 11 | const templatePath = path.resolve(__dirname, "webpack.config.tpl"); 12 | const destPath = path.resolve(appPath, "webpack.config.js"); 13 | 14 | return new Promise((resolve, reject) => { 15 | let stream = fs.createReadStream(templatePath) 16 | .pipe(entry(params)) 17 | .pipe(extensions(params)) 18 | .pipe(rules(params)) 19 | .pipe(plugins(params)) 20 | .pipe(fs.createWriteStream(destPath)); 21 | 22 | stream.on("finish", resolve); 23 | stream.on("error", reject); 24 | }); 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-wpapp", 3 | "version": "0.2.0", 4 | "description": "Boost your workflow with a simple and configurable webpack project generator", 5 | "bin": { 6 | "create-wpapp": "./bin/index.js" 7 | }, 8 | "keywords": [ 9 | "webpack", 10 | "bootstrap", 11 | "project", 12 | "starter", 13 | "generator" 14 | ], 15 | "author": { 16 | "name": "Udi Talias", 17 | "email": "udi.talias@gmail.com", 18 | "url": "https://github.com/uditalias" 19 | }, 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/uditalias/create-wpapp/issues" 23 | }, 24 | "homepage": "https://github.com/uditalias/create-wpapp#readme", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/uditalias/create-wpapp.git" 28 | }, 29 | "dependencies": { 30 | "chalk": "2.3.0", 31 | "fs-extra": "^4.0.2", 32 | "inquirer": "3.3.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bin/handlers/dotFiles/dotFilesHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const GIT_IGNORE = [ 5 | ".vscode", 6 | ".idea", 7 | ".DS_Store", 8 | "dist", 9 | "node_modules", 10 | "package-lock.json" 11 | ]; 12 | 13 | module.exports = function (appPath, params) { 14 | 15 | fs.writeFileSync( 16 | path.resolve(appPath, ".gitignore"), 17 | GIT_IGNORE.join("\n") 18 | ); 19 | 20 | if (params.type === "javascript") { 21 | const babelRc = { 22 | presets: [ 23 | "es2015", 24 | "stage-0" 25 | ], 26 | plugins: [ 27 | "transform-runtime" 28 | ] 29 | }; 30 | 31 | if (params.use_react) { 32 | babelRc.presets.push("react"); 33 | } 34 | 35 | fs.writeFileSync( 36 | path.resolve(appPath, ".babelrc"), 37 | JSON.stringify(babelRc, null, 4) 38 | ); 39 | } 40 | 41 | return Promise.resolve(); 42 | } -------------------------------------------------------------------------------- /bin/handlers/sourceFiles/tasks/imports.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=ENTRY.IMPORTS=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | let imports = []; 13 | 14 | if (params.use_css) { 15 | imports.push(`import "style/main.${params.use_scss ? "scss" : "css"}"`); 16 | } 17 | 18 | if (params.use_react) { 19 | if (params.type === "typescript") { 20 | imports.push(`import * as React from "react"`); 21 | } else if (params.type === "javascript") { 22 | imports.push(`import React from "react"`); 23 | } 24 | 25 | imports.push(`import { render } from "react-dom"`); 26 | } 27 | 28 | this.push(file.replace(new RegExp(KEY, "g"), imports.join("\n"))); 29 | 30 | cb(); 31 | } 32 | 33 | return stream; 34 | } -------------------------------------------------------------------------------- /bin/createWpapp.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const chalk = require("chalk"); 3 | const AppGenerator = require("./appGenerator"); 4 | const setup = require("./setup"); 5 | const { APP_PATH } = require("./params"); 6 | 7 | let generator = null; 8 | 9 | /** 10 | * Get all user inputs from setup and create the app generator 11 | * 12 | * @param {any} answers 13 | */ 14 | function createApp(answers) { 15 | 16 | console.log(`Creating a new webpack app in ${chalk.green(APP_PATH)}...`); 17 | 18 | generator = new AppGenerator(answers, APP_PATH); 19 | 20 | generator.on("finish", onFinish); 21 | 22 | generator.generate(); 23 | } 24 | 25 | function onFinish(err) { 26 | if (err) { 27 | console.log(chalk.red("Failed to create webpack app!")); 28 | console.log(chalk.red(err.stack)); 29 | 30 | process.exit(1); 31 | } 32 | 33 | console.log(chalk.green("All Done.")); 34 | console.log(`Run ${chalk.green(`npm start`)} to start the dev server`); 35 | 36 | process.exit(0); 37 | } 38 | 39 | 40 | inquirer.prompt(setup).then(createApp); -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Udi Talias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /bin/handlers/tsConfig/tsConfigHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | module.exports = function (appPath, params) { 5 | 6 | if (params.type !== "typescript") { 7 | return Promise.resolve(); 8 | } 9 | 10 | const tsConfig = { 11 | compilerOptions: { 12 | baseUrl: "src/", 13 | target: "es5", 14 | module: "commonjs", 15 | allowJs: true, 16 | allowSyntheticDefaultImports: true, 17 | importHelpers: true, 18 | sourceMap: true, 19 | moduleResolution: "node", 20 | lib: [ 21 | "es6", 22 | "es7", 23 | "dom" 24 | ], 25 | types: [ 26 | "webpack-env" 27 | ], 28 | noUnusedLocals: false, 29 | strictNullChecks: false, 30 | removeComments: false 31 | }, 32 | include: [ 33 | "src/**/*" 34 | ], 35 | exclude: [ 36 | "node_modules" 37 | ], 38 | compileOnSave: false 39 | }; 40 | 41 | if (params.use_react) { 42 | tsConfig.compilerOptions.types.push("react"); 43 | tsConfig.compilerOptions.types.push("react-dom"); 44 | tsConfig.compilerOptions.jsx = "react"; 45 | } 46 | 47 | fs.writeFileSync( 48 | path.resolve(appPath, "tsconfig.json"), 49 | JSON.stringify(tsConfig, null, 4) 50 | ); 51 | 52 | return Promise.resolve(); 53 | } -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/webpack.config.tpl: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const packageConfig = require('./package.json'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const path = require('path'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const contextPath = path.join(__dirname, './src'); 7 | const appEnv = process.env.NODE_ENV || 'development'; 8 | const isProduction = appEnv == 'production'; 9 | const isDevelopment = appEnv == 'development'; 10 | const destinationDir = (isProduction) ? `./dist` : `./build`; 11 | 12 | module.exports = { 13 | 14 | context: contextPath, 15 | 16 | devtool: "#source-map", 17 | 18 | entry: { 19 | app: <@=ENTRY.FILE=@> 20 | }, 21 | 22 | resolve: { 23 | extensions: <@=RESOLVE.EXTENSIONS=@>, 24 | 25 | modules: [contextPath, "node_modules"] 26 | }, 27 | 28 | output: { 29 | path: path.resolve(__dirname, destinationDir), 30 | filename: 'bundle.js' 31 | }, 32 | 33 | module: { 34 | rules: <@=MODULE.RULES=@> 35 | }, 36 | 37 | plugins: [ 38 | 39 | new HtmlWebpackPlugin({ 40 | inject: false, 41 | template: '../views/index.html' 42 | }), 43 | 44 | new webpack.NoEmitOnErrorsPlugin(), 45 | 46 | new webpack.ProvidePlugin({ 47 | 'Promise': 'es6-promise' 48 | }), 49 | 50 | new webpack.DefinePlugin({ 51 | 'process.env.NODE_ENV': JSON.stringify(appEnv), 52 | 'process.env.VERSION': JSON.stringify(packageConfig.version) 53 | }), 54 | 55 | <@=PLUGINS=@> 56 | ] 57 | }; 58 | -------------------------------------------------------------------------------- /bin/handlers/webpackConfig/tasks/rules.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require("stream"); 2 | 3 | module.exports = function (params) { 4 | 5 | const stream = Transform({ objectMode: true }); 6 | const KEY = "<@=MODULE.RULES=@>"; 7 | 8 | stream._transform = function (file, encoding, cb) { 9 | 10 | file = file.toString(); 11 | 12 | let rules = []; 13 | 14 | if (params.type === "typescript") { 15 | 16 | const ext = params.use_react ? "tsx" : "ts"; 17 | 18 | rules.push(`{ 19 | test: /\.${ext}?$/, 20 | use: ['ts-loader'], 21 | exclude: /node_modules/ 22 | }`); 23 | 24 | } else if (params.type === "javascript") { 25 | 26 | rules.push(`{ 27 | test: /\.js?$/, 28 | use: ['babel-loader'], 29 | exclude: /node_modules/ 30 | }`); 31 | 32 | } 33 | 34 | if (params.use_css) { 35 | 36 | let ext = "css"; 37 | 38 | const loaders = [ 39 | "css-loader" 40 | ]; 41 | 42 | if (params.use_scss) { 43 | ext = "scss" 44 | loaders.push("sass-loader"); 45 | } 46 | 47 | rules.push(`{ 48 | test: /\.${ext}$/, 49 | use: ExtractTextPlugin.extract({ 50 | fallback: 'style-loader', 51 | use: ${JSON.stringify(loaders)} 52 | }) 53 | }`); 54 | 55 | } 56 | 57 | this.push(file.replace(new RegExp(KEY, "g"), `[${rules.join(", ")}]`)); 58 | 59 | cb(); 60 | } 61 | 62 | return stream; 63 | } -------------------------------------------------------------------------------- /bin/handlers/sourceFiles/sourceFilesHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const fse = require("fs-extra"); 3 | const path = require("path"); 4 | 5 | const imports = require("./tasks/imports"); 6 | 7 | module.exports = function (appPath, params) { 8 | 9 | const baseDestPath = path.resolve(appPath, "src"); 10 | const entryTemplatePath = path.resolve(__dirname, ( 11 | params.use_react ? "entry.x.tpl" : "entry.tpl" 12 | )); 13 | 14 | fse.ensureDirSync(baseDestPath); 15 | 16 | let entryExt = "js"; 17 | 18 | if (params.type === "typescript") { 19 | entryExt = "ts"; 20 | 21 | if (params.use_react) { 22 | entryExt = "tsx"; 23 | } 24 | } 25 | 26 | const entryDestPath = path.resolve(baseDestPath, `app.${entryExt}`); 27 | 28 | const tasks = []; 29 | 30 | if (params.use_css) { 31 | const styleDestPath = path.resolve(appPath, "src/style"); 32 | 33 | fse.ensureDirSync(styleDestPath); 34 | 35 | tasks.push(new Promise((resolve, reject) => { 36 | let ext = params.use_scss ? "scss" : "css"; 37 | 38 | let styleStream = fs.createReadStream(path.resolve(__dirname, "style.tpl")) 39 | .pipe(fs.createWriteStream(path.resolve(styleDestPath, `main.${ext}`))); 40 | 41 | styleStream.on("error", reject); 42 | styleStream.on("finish", resolve); 43 | })); 44 | } 45 | 46 | tasks.push(new Promise((resolve, reject) => { 47 | const entryStream = fs.createReadStream(entryTemplatePath) 48 | .pipe(imports(params)) 49 | .pipe(fs.createWriteStream(entryDestPath)); 50 | 51 | entryStream.on("error", reject); 52 | entryStream.on("finish", resolve); 53 | })); 54 | 55 | return Promise.all(tasks); 56 | } -------------------------------------------------------------------------------- /bin/appGenerator.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require("events"); 2 | const handlers = require("./handlers"); 3 | const fse = require('fs-extra') 4 | 5 | /** 6 | * Creates a webpack app at given `appPath` with user `params` 7 | * 8 | * @class AppGenerator 9 | * @extends {EventEmitter} 10 | */ 11 | class AppGenerator extends EventEmitter { 12 | 13 | /** 14 | * Creates an instance of AppGenerator. 15 | * @param {any} params - user params from setup process 16 | * @param {any} appPath - destination app path 17 | * @memberof AppGenerator 18 | */ 19 | constructor(params, appPath) { 20 | super(); 21 | 22 | this._params = params; 23 | this._appPath = appPath; 24 | } 25 | 26 | /** 27 | * Process all handlers to create app files 28 | * and installs app dependencies 29 | * 30 | * @memberof AppGenerator 31 | */ 32 | generate() { 33 | const tasks = []; 34 | 35 | for (let handlerName in handlers) { 36 | tasks.push(handlers[handlerName](this._appPath, this._params)); 37 | } 38 | 39 | Promise.all(tasks) 40 | .then(this._tasksSuccess.bind(this)) 41 | .catch(this._tasksError.bind(this)); 42 | } 43 | 44 | /** 45 | * Fires the `finish` event with no error when all 46 | * handlers finished successfully 47 | * 48 | * @memberof AppGenerator 49 | */ 50 | _tasksSuccess() { 51 | this.emit("finish", null); 52 | } 53 | 54 | /** 55 | * Fires the `finish` event with error when one 56 | * of the handlers throws one 57 | * 58 | * @param {any} err 59 | * @memberof AppGenerator 60 | */ 61 | _tasksError(err) { 62 | 63 | this._cleanup(); 64 | 65 | this.emit("finish", err); 66 | } 67 | 68 | /** 69 | * Clears the `appPath` from files and folders 70 | * when the process has failed 71 | * 72 | * @memberof AppGenerator 73 | */ 74 | _cleanup() { 75 | fse.emptyDirSync(this._appPath); 76 | } 77 | } 78 | 79 | module.exports = AppGenerator; -------------------------------------------------------------------------------- /bin/setup.js: -------------------------------------------------------------------------------- 1 | const { 2 | GIT_USER_NAME, 3 | GIT_USER_EMAIL 4 | } = require("./params"); 5 | const { isValidHTTPPort } = require("./utils"); 6 | 7 | module.exports = [ 8 | { 9 | type: "input", 10 | name: "app_name", 11 | message: "What is your app name?", 12 | default: "webpack-starter", 13 | validate: function (input) { 14 | const done = this.async(); 15 | 16 | if (~input.indexOf(' ')) { 17 | done("No spaces please"); 18 | return; 19 | } 20 | 21 | done(null, true); 22 | } 23 | }, 24 | { 25 | type: "input", 26 | name: "app_port", 27 | message: "Select dev server port:", 28 | default: "3001", 29 | validate: function (input) { 30 | const done = this.async(); 31 | 32 | if (!isValidHTTPPort(input)) { 33 | done("Port is not valid"); 34 | return; 35 | } 36 | 37 | done(null, true); 38 | } 39 | }, 40 | { 41 | type: "list", 42 | name: "type", 43 | message: "Choose app type:", 44 | choices: [ 45 | { name: "TypeScript", value: "typescript" }, 46 | { name: "ES2015", value: "javascript" } 47 | ] 48 | }, 49 | { 50 | type: "confirm", 51 | name: "use_react", 52 | message: "Are you going to use React in your app?", 53 | default: false 54 | }, 55 | { 56 | type: "confirm", 57 | name: "use_css", 58 | message: "Are you going to use css in your app?", 59 | default: true 60 | }, 61 | { 62 | type: "confirm", 63 | name: "use_scss", 64 | message: "Do you want to use SCSS preprocessor?", 65 | default: true, 66 | when: function (answers) { 67 | return answers.use_css; 68 | } 69 | }, 70 | { 71 | type: "input", 72 | name: "user_name", 73 | message: "What is your name?", 74 | default: GIT_USER_NAME 75 | }, 76 | { 77 | type: "input", 78 | name: "user_email", 79 | message: "What is your email?", 80 | default: GIT_USER_EMAIL 81 | } 82 | ]; -------------------------------------------------------------------------------- /bin/handlers/packageJson/packageJsonHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { spawnSync } = require("child_process"); 4 | 5 | const BABEL_DEV_DEPS = [ 6 | "babel-core", 7 | "babel-loader", 8 | "babel-plugin-transform-runtime", 9 | "babel-preset-es2015", 10 | "babel-preset-stage-0", 11 | "babel-runtime", 12 | ]; 13 | 14 | const TYPESCRIPT_DEV_DEPS = [ 15 | "typescript", 16 | "tslib", 17 | "ts-loader", 18 | "@types/webpack-env" 19 | ]; 20 | 21 | const CSS_DEV_DEPS = [ 22 | "css-loader", 23 | "style-loader" 24 | ]; 25 | 26 | const SASS_DEV_DEPS = [ 27 | "node-sass", 28 | "sass-loader" 29 | ]; 30 | 31 | const COMMON_DEV_DEPS = [ 32 | "es6-promise", 33 | "extract-text-webpack-plugin", 34 | "html-webpack-plugin", 35 | "webpack", 36 | "webpack-dev-server" 37 | ]; 38 | 39 | const REACT_DEV_DEPS = [ 40 | "react", 41 | "react-dom", 42 | "prop-types" 43 | ]; 44 | 45 | const REACT_TYPESCRIPT_DEV_DEPS = [ 46 | "@types/react", 47 | "@types/react-dom" 48 | ] 49 | 50 | module.exports = function (appPath, params) { 51 | 52 | const package = { 53 | name: params.app_name, 54 | version: "1.0.0", 55 | description: "", 56 | author: { 57 | name: params.user_name, 58 | email: params.user_email 59 | }, 60 | scripts: { 61 | start: `NODE_ENV=development webpack-dev-server --hot --progress --port ${params.app_port}`, 62 | build: "NODE_ENV=production webpack -p" 63 | }, 64 | license: "ISC", 65 | devDependencies: {} 66 | }; 67 | 68 | fs.writeFileSync( 69 | path.resolve(appPath, "package.json"), 70 | JSON.stringify(package, null, 4) 71 | ); 72 | 73 | let devDependencies = [].concat(COMMON_DEV_DEPS); 74 | 75 | switch (params.type) { 76 | case "javascript": 77 | devDependencies.push(...BABEL_DEV_DEPS); 78 | 79 | if (params.use_react) { 80 | devDependencies.push("babel-preset-react"); 81 | } 82 | 83 | break; 84 | case "typescript": 85 | devDependencies.push(...TYPESCRIPT_DEV_DEPS); 86 | 87 | if (params.use_react) { 88 | devDependencies.push(...REACT_TYPESCRIPT_DEV_DEPS); 89 | } 90 | break; 91 | } 92 | 93 | if (params.use_react) { 94 | devDependencies.push(...REACT_DEV_DEPS); 95 | } 96 | 97 | if (params.use_css) { 98 | devDependencies.push(...CSS_DEV_DEPS); 99 | } 100 | 101 | if (params.use_scss) { 102 | devDependencies.push(...SASS_DEV_DEPS); 103 | } 104 | 105 | if (devDependencies.length > 0) { 106 | spawnSync("npm", ["install", "--save-dev"].concat(devDependencies), { stdio: "inherit" }); 107 | } 108 | 109 | return Promise.resolve(); 110 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-wpapp 2 | ## Boost your workflow with a simple and configurable webpack project generator 3 | 4 | ### Disclaimer 5 | This is not a replacement for `create-react-app` by any means! It's just a way to startup a webpack project with basic configuration in seconds. 6 | 7 | ### Why? 8 | A new UI framework just came out, a new library, a new tool.. you want to test it but you need to config webpack and install lots of stuff in order to start.. 😤 9 | 10 | There's lots of webpack app generators, but this one won't make you cry. 11 | 12 | Like `create-react-app` which is awesome but very opinionated... have you tried adding something to the `webpack.config.js` file? I know... you must `eject` everything in order to do so. 13 | 14 | What if all I want to do is just write my code, maybe add a webpack plugin or a loader so I can run it in my browser? 15 | 16 | - You want `babel` to transform your ES6 code? OK! 17 | - You want TypeScript instead? OK! 18 | - You want `react`? OK! 19 | - You want to style your app with css? OK! 20 | - You want to work with SCSS preprocessor? OK!!! 21 | 22 | I put all my opinions aside and focused on simplicity, and guess what? you don't need to eject anything in order to edit/add/remove something so you can just start writing your code! **THAT'S - IT!** 23 | 24 | 25 | ### Install? 26 | 27 | `npm install -g create-wpapp` 28 | 29 | ### How does it work? 30 | 31 | ``` 32 | $ mkdir project 33 | $ cd project 34 | $ create-wpapp 35 | 36 | ? What is your app name? webpack-starter 37 | ? Select dev server port: 3001 38 | ? Choose app type: TypeScript 39 | ? Are you going to use React in your app? Yes 40 | ? Are you going to use css in your app? Yes 41 | ? Do you want to use SCSS preprocessor? Yes 42 | ? What is your name? Udi Talias 43 | ? What is your email? udi.talias@gmail.com 44 | Creating a new webpack app in /Users/udidu/project... 45 | ... 46 | All Done. 47 | Run npm start to start the dev server 48 | ``` 49 | 50 | This will create a webpack project with dev server hmr in the following structure: 51 | 52 | ``` 53 | /project 54 | |- node_modules 55 | |- src 56 | |- style 57 | |- main.{css,scss} // Based on your selection 58 | |- app.{js,ts,tsx} // Based on your selection 59 | |- views 60 | |- index.html 61 | |- .gitignore 62 | |- package.json 63 | |- webpack.config.js 64 | |- tsconfig.json // For a TypeScript project 65 | 66 | ``` 67 | 68 | Just run `npm start` to start the dev server. 69 | 70 | 71 | ### Contribute? 72 | 73 | Just fork, do what you want and [create a pull request](https://github.com/uditalias/create-wpapp/compare?expand=1). 74 | 75 | ### Issue? 76 | 77 | Just [open one](https://github.com/uditalias/create-wpapp/issues/new). 78 | 79 | 80 | 81 | This tool will evolve from time to time... I know it's very simple but tests and more options will be added to the setup process so you can choose (or not) what you want in your **dead simple webpack project!** 82 | 83 | 84 | 85 | 86 | --- 87 | ## License 88 | 89 | Licensed under the MIT License (Basically - do anything you want license). See [License](LICENSE) for more details. 90 | --------------------------------------------------------------------------------