├── .editorconfig ├── .eslintignore ├── .flowconfig ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmrc ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── CHANGES.md ├── CHANGES.old.md ├── LICENSE ├── README.md ├── TODO ├── appveyor.yml ├── docs ├── BrowserSupport.md ├── Commands.md ├── Configuration.md ├── Examples.md ├── FAQ.md ├── Features.md ├── ImplementingPlugins.md ├── Middleware.md ├── Plugins.md ├── README.md ├── Stylesheets.md ├── Testing.md ├── Versioning.md └── guides │ ├── QuickDevelopment.md │ ├── README.md │ ├── ReactApps.md │ ├── ReactComponents.md │ └── resources │ ├── react-app-build-dist.png │ ├── react-app-build.png │ ├── react-app-default.png │ ├── react-app-test-coverage-html.png │ ├── react-app-test-coverage.png │ ├── react-component-build.png │ ├── react-component-demo.gif │ ├── react-component-serve-error-browser.png │ ├── react-component-serve-error-console.png │ ├── react-component-test-coverage-html.png │ ├── react-component-test-coverage.png │ ├── react-serve-app-error-browser.png │ ├── react-serve-app-error-console.png │ ├── react-serve-app-hmr-css.gif │ ├── react-serve-app-hmr-js.gif │ ├── react-serve-app-render-error.png │ └── react-serve.png ├── express.js ├── flow-typed ├── libdefs.js └── npm │ ├── debug_v2.x.x.js │ ├── expect_v1.x.x.js │ ├── minimist_v1.x.x.js │ ├── mocha_v3.1.x.js │ ├── run-series_v1.x.x.js │ └── semver_v5.1.x.js ├── package-changelog.js ├── package.json ├── resources ├── auto-install.gif ├── config.dot ├── cover.jpg ├── linux.png └── windows.png ├── src ├── WebpackStatusPlugin.js ├── appCommands.js ├── bin │ └── nwb.js ├── cli.js ├── commands │ ├── build-demo.js │ ├── build-inferno-app.js │ ├── build-inferno.js │ ├── build-preact-app.js │ ├── build-preact.js │ ├── build-react-app.js │ ├── build-react-component.js │ ├── build-react.js │ ├── build-web-app.js │ ├── build-web-module.js │ ├── build-web.js │ ├── build.js │ ├── check-config.js │ ├── clean-app.js │ ├── clean-demo.js │ ├── clean-module.js │ ├── clean.js │ ├── inferno.js │ ├── init.js │ ├── new.js │ ├── preact.js │ ├── react.js │ ├── serve-inferno-app.js │ ├── serve-inferno.js │ ├── serve-preact-app.js │ ├── serve-preact.js │ ├── serve-react-app.js │ ├── serve-react-demo.js │ ├── serve-react.js │ ├── serve-web-app.js │ ├── serve-web.js │ ├── serve.js │ ├── test-inferno.js │ ├── test-preact.js │ ├── test-react-component.js │ ├── test-react.js │ ├── test-web-module.js │ ├── test.js │ └── web.js ├── config │ ├── UserConfigReport.js │ ├── babel.js │ ├── index.js │ ├── karma.js │ ├── npm.js │ ├── plugin.js │ ├── user.js │ └── webpack.js ├── constants.js ├── createBabelConfig.js ├── createKarmaConfig.js ├── createProject.js ├── createServerWebpackConfig.js ├── createWebpackConfig.js ├── debug.js ├── devServer.js ├── errors.js ├── expressMiddleware.js ├── inferno │ ├── index.js │ ├── inferno-preset.js │ └── renderShim.js ├── karmaServer.js ├── moduleBuild.js ├── preact │ ├── index.js │ ├── preact-preset.js │ └── renderShim.js ├── quickCommands.js ├── react │ ├── index.js │ └── renderShim.js ├── types.js ├── utils.js ├── web │ └── index.js ├── webpackBuild.js ├── webpackServer.js └── webpackUtils.js ├── templates ├── inferno-app │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── nwb.config.js │ ├── public │ │ └── _.gitkeep │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── index.css │ │ ├── index.html │ │ ├── index.js │ │ └── inferno.svg │ └── tests │ │ ├── App.test.js │ │ └── _.eslintrc ├── preact-app │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── nwb.config.js │ ├── public │ │ └── _.gitkeep │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── index.css │ │ ├── index.html │ │ ├── index.js │ │ ├── preact-logo.svg │ │ └── preact-name.svg │ └── tests │ │ ├── App.test.js │ │ └── _.eslintrc ├── react-app │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── nwb.config.js │ ├── public │ │ └── _.gitkeep │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── index.css │ │ ├── index.html │ │ ├── index.js │ │ └── react.svg │ └── tests │ │ ├── App.test.js │ │ └── _.eslintrc ├── react-component │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── demo │ │ └── src │ │ │ └── index.js │ ├── nwb.config.js │ ├── src │ │ └── index.js │ └── tests │ │ ├── _.eslintrc │ │ └── index.test.js ├── web-app │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── nwb.config.js │ ├── public │ │ └── _.gitkeep │ ├── src │ │ ├── index.html │ │ └── index.js │ └── tests │ │ ├── _.eslintrc │ │ └── index.test.js ├── web-module │ ├── CONTRIBUTING.md │ ├── README.md │ ├── _.gitignore │ ├── _.travis.yml │ ├── _package.json │ ├── nwb.config.js │ ├── src │ │ └── index.js │ └── tests │ │ ├── _.eslintrc │ │ └── index.test.js └── webpack-template.html └── tests ├── .eslintrc ├── commands ├── build-test.js ├── new-init-test.js ├── projects-test.js ├── serve-test.js └── test-test.js ├── config-test.js ├── createBabelConfig-test.js ├── createKarmaConfig-test.js ├── createProject-test.js ├── createWebpackConfig-test.js ├── fixtures ├── bad-config.js ├── invalid-config.js ├── plugins │ ├── node_modules │ │ └── nwb-testplugin │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── projects │ ├── async-await │ │ ├── _package.json │ │ ├── nwb.config.js │ │ ├── src │ │ │ ├── App.js │ │ │ ├── index.html │ │ │ └── index.js │ │ └── tests │ │ │ ├── _.eslintrc │ │ │ └── index.test.js │ ├── cherry-pick │ │ ├── _package.json │ │ ├── nwb.config.js │ │ └── src │ │ │ ├── __tests__ │ │ │ └── helper.js │ │ │ ├── index.js │ │ │ ├── index.spec.js │ │ │ └── index.test.js │ ├── express-middleware │ │ ├── _package.json │ │ ├── nwb.config.js │ │ ├── server.js │ │ └── src │ │ │ ├── App.js │ │ │ ├── index.html │ │ │ └── index.js │ ├── failing-build │ │ ├── _package.json │ │ ├── nwb.config.js │ │ └── src │ │ │ └── index.js │ ├── react-component-with-css │ │ ├── _package.json │ │ ├── nwb.config.js │ │ └── src │ │ │ ├── components │ │ │ ├── Thing.css │ │ │ ├── Thing.js │ │ │ └── Thing.spec.js │ │ │ └── index.js │ └── router-app │ │ ├── _package.json │ │ ├── nwb.config.js │ │ ├── public │ │ ├── favicon.ico │ │ └── subdir │ │ │ └── shyamalan.jpg │ │ └── src │ │ ├── components │ │ └── Thing.js │ │ ├── index.html │ │ ├── index.js │ │ └── routes │ │ ├── App.css │ │ ├── App.js │ │ ├── child1 │ │ └── Child1.js │ │ ├── child2 │ │ ├── Child2.css │ │ └── Child2.js │ │ └── root.js └── worst-config.js └── utils-test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace=true 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /flow-typed 2 | /lib 3 | /node_modules 4 | /templates 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /flow-typed/libdefs.js 3 | /lib/.* 4 | /node_modules/.* 5 | /templates/.* 6 | /tests/fixtures/.* 7 | 8 | [include] 9 | 10 | [libs] 11 | ./flow-typed/libdefs.js 12 | 13 | [options] 14 | module.ignore_non_literal_requires=true 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.dot diff 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | This issue is a: 2 | 3 | - Bug report 4 | - Feature request 5 | - Question / support request 6 | - Other 7 | 8 | 11 | 12 | 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.nyc_output 2 | /coverage 3 | /lib 4 | /node_modules 5 | npm-debug.log* 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save=false 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | - 12 7 | - 14 8 | 9 | cache: 10 | directories: 11 | - $HOME/.npm 12 | 13 | before_install: 14 | - npm install coveralls 15 | - git config --global user.email "jonathan.buchanan@gmail.com" 16 | - git config --global user.name "Jonny Buchanan" 17 | 18 | script: "npm run test:coverage" 19 | 20 | after_success: 21 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 22 | 23 | branches: 24 | only: 25 | - master 26 | - next 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dzannotti.vscode-babel-coloring", 4 | "EditorConfig.editorconfig", 5 | "dbaeumer.vscode-eslint", 6 | "flowtype.flow-for-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.options": { 3 | "configFile": "node_modules/eslint-config-jonnybuchanan/.eslintrc" 4 | }, 5 | "eslint.nodePath": "node_modules/eslint-config-jonnybuchanan/node_modules", 6 | "flow.useNPMPackagedFlow": true 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Jonny Buchanan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | Uses some code from create-react-app: 21 | 22 | BSD License 23 | 24 | For create-react-app software 25 | 26 | Copyright (c) 2016-present, Facebook, Inc. All rights reserved. 27 | 28 | Redistribution and use in source and binary forms, with or without modification, 29 | are permitted provided that the following conditions are met: 30 | 31 | * Redistributions of source code must retain the above copyright notice, this 32 | list of conditions and the following disclaimer. 33 | 34 | * Redistributions in binary form must reproduce the above copyright notice, 35 | this list of conditions and the following disclaimer in the documentation 36 | and/or other materials provided with the distribution. 37 | 38 | * Neither the name Facebook nor the names of its contributors may be used to 39 | endorse or promote products derived from this software without specific 40 | prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 43 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 44 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 45 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 46 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 47 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 48 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 49 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 50 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 51 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 52 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | ☐ Add `babel.assumptions` config 2 | https://babeljs.io/assumptions 3 | ☐ Configure browser targets in Webpack's top-level `target` option: 4 | https://webpack.js.org/configuration/target/#browserslist 5 | browserslist:${browserslist query} 6 | ☐ Use webpack 5's new asset modules instead of url-loader and file-loader 7 | https://webpack.js.org/guides/asset-modules/ 8 | ☐ Check if npm-install-plugin's approach still works in Webpack 5 and fix it if not -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - next 5 | 6 | environment: 7 | matrix: 8 | - nodejs_version: "12" 9 | 10 | install: 11 | - ps: Install-Product node $env:nodejs_version x64 12 | - npm install 13 | 14 | test_script: 15 | - git config --global user.email "jonathan.buchanan@gmail.com" 16 | - git config --global user.name "Jonny Buchanan" 17 | - node --version 18 | - npm --version 19 | - npm run test:coverage 20 | 21 | build: off 22 | -------------------------------------------------------------------------------- /docs/BrowserSupport.md: -------------------------------------------------------------------------------- 1 | ## Browser Support 2 | 3 | nwb's default configuration supports modern browsers. Support for Internet Explorer 9, 10 and 11 requires [polyfills](#supporting-internet-explorer). 4 | 5 | ### Default Browser Support 6 | 7 | nwb uses the following [Browserslist](https://github.com/browserslist/browserslist#browserslist-) queries by default: 8 | 9 | - [`last 1 chrome version, last 1 firefox version, last 1 safari version`](https://browserl.ist/?q=last+1+chrome+version%2C+last+1+firefox+version%2C+last+1+safari+version) for development, when running the development server with `nwb serve` (or quick commands such as `npm react run`) 10 | - [`>0.2%, not dead, not op_mini all`](https://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+op_mini+all) for production, when creating a build with `nwb build` (or quick commands such as `npm react build`) 11 | 12 | > Use the links above to check which browsers and versions these queries currently resolve to. 13 | 14 | These are used to configure: 15 | 16 | - [Babel's `targets` option](https://babeljs.io/docs/en/options#targets), so it only transpiles the necessary ECMAScript 2015+ for supported browsers. 17 | - [Autoprefixer's `overrideBrowserslist` option](https://github.com/postcss/autoprefixer#options), so it only includes the necessary CSS prefixes for supported browsers. 18 | 19 | ### Configuring Browser Support 20 | 21 | If your app needs to support more (or fewer!) browsers, you can tweak browser support settings using [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object). 22 | 23 | Broadening the range of supported browsers will ensure your app works for everone who needs to use it, while narrowing the range may help decrease your bundle sizes, if less code needs to be transpiled and fewer Babel helpers need to be imported. 24 | 25 | For example, IE9 is considered a "dead" browser in Browserslist queries, so if you needed to support it, you could specifically enable it in [`browsers.production` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object) like so: 26 | 27 | ```js 28 | module.exports = { 29 | browsers { 30 | production: '>0.2%, not dead, not op_mini all, ie 9' 31 | } 32 | } 33 | ``` 34 | 35 | You can see that [IE 9 has now been added](https://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+op_mini+all%2C+ie+9) to the list of supported browsers. 36 | 37 | ## Polyfilling Missing Language Features 38 | 39 | ### Supporting Internet Explorer 40 | 41 | [react-app-polyfill](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#react-app-polyfill) provides convenient collection of polyfills for IE9 and IE11. 42 | 43 | If you need to support Internet Explorer, install react-app-polyfill and import the appropriate polyfill entry point as the first thing in your app's entry point (usually `src/index.js`): 44 | 45 | ``` 46 | npm install react-app-polyfill 47 | ``` 48 | ```js 49 | import 'react-app-polyfill/ie11' 50 | ``` 51 | 52 | See [react-app-polyfill's Supporting Internet Explorer docs](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#supporting-internet-explorer) for more details. 53 | 54 | ### Manual Polyfilling 55 | 56 | If there are specific language features missing from one of your supported browsers, you can polyfill them manually by installing [core-js](https://github.com/zloirock/core-js#core-js) and importing the appropriate polyfills at the top of your app's entry point (usually `src/index.js`). 57 | 58 | e.g. if you want to use [`Object.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values) in your app, but one of your target browsers doesn't support it: 59 | 60 | ``` 61 | npm install core-js 62 | ``` 63 | ```js 64 | import 'core-js/features/object/values' 65 | ``` 66 | 67 | ### Automatic Polyfilling 68 | 69 | nwb configures `@babel/preset-env`'s [`useBuiltins: 'entry'` option](https://babeljs.io/docs/en/next/babel-preset-env), which will look for a core-js entry point import in your code and replace it with a specific list of polyfill imports to cover the range of supported browsers. See the [core-js docs for an example of this feature in action](https://github.com/zloirock/core-js#babelpreset-env). 70 | 71 | To make use of this, import a core-js entry point at the top of your app's entry point (usually `src/index.js`): 72 | 73 | ```js 74 | import 'core-js/stable' 75 | ``` 76 | 77 | react-app-polyfill also provides [an entry point for polyfilling stable language features using core-js](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#polyfilling-other-language-features), so nwb's Babel config supports transpiling react-app-polyfill to allow `@babel/preset-env` to do its thing: 78 | 79 | ```js 80 | import 'react-app-polyfill/stable' 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | ## Frequently Asked Questions 2 | 3 | - [What does "nwb" stand for?](#what-does-nwb-stand-for) 4 | - [How can I view the configuration nwb generates?](#how-can-i-view-the-configuration-nwb-generates) 5 | - [How do I enable CSS Modules?](#how-do-i-enable-css-modules) 6 | - [What can I configure to reduce bundle size?](#what-can-i-configure-to-reduce-bundle-size) 7 | - [How can I copy non-JavaScript files when building a React component/library?](#how-can-i-copy-non-javascript-files-when-building-a-react-componentlibrary) 8 | - [How can I use React Hot Loader instead of React Transform?](#how-can-i-use-react-hot-loader-instead-of-react-transform) 9 | - [How can I debug using VS Code when using nwb?](#how-can-i-debug-using-vs-code-when-using-nwb) 10 | 11 | --- 12 | 13 | ### What does "nwb" stand for? 14 | 15 | Shortness and ease of typing. 16 | 17 | It uses **N**ode.js, **W**ebpack and **B**abel to **b**uild apps for the **w**eb and modules for **n**pm. 18 | 19 | `nwb` sounded like the best combination of those and was easy to type. 20 | 21 | ### How can I view the configuration nwb generates? 22 | 23 | Set the `DEBUG` environment variable to `nwb` before running to check what generated configuration looks like: 24 | 25 | ``` 26 | # *nix 27 | export DEBUG=nwb 28 | # Windows 29 | set DEBUG=nwb 30 | ``` 31 | 32 | If you need to prevent server commands from clearing scrollback so you can read any unexpected error logging which is happening, pass a `--no-clear` flag when running the development server: 33 | 34 | ``` 35 | # When running nwb via npm scripts 36 | npm start -- --no-clear 37 | # When running nwb serve directly 38 | nwb serve --no-clear 39 | ``` 40 | 41 | ### How do I enable CSS Modules? 42 | 43 | Use [`webpack.rules` config](/docs/Configuration.md#rules-object) in `nwb.config.js` to [configure `css-loader` in the default stylesheet rule](/docs/Stylesheets.md#default-stylesheet-rules) with the necessary [`css-loader` options](https://github.com/webpack-contrib/css-loader#options): 44 | 45 | ```js 46 | module.exports = { 47 | webpack: { 48 | rules: { 49 | css: { 50 | modules: true, 51 | localIdentName: ( 52 | process.env.NODE_ENV === 'production' 53 | ? '[path][name]-[local]-[hash:base64:5]' 54 | : '[hash:base64:5]' 55 | ) 56 | } 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | If you only need CSS Modules for some of the stylesheets you'll be importing, you can configure [custom stylesheet rules](/docs/Stylesheets.md#custom-stylesheet-rules). 63 | 64 | ### What can I configure to reduce bundle size? 65 | 66 | #### Enable cherry-picking for destructuring imports 67 | 68 | If you're using destructuring imports with libraries like React Router and React Bootstrap (e.g. `import {Button} from 'react-bootstrap'`), you're bundling the whole library, instead of just the bits you need. 69 | 70 | Try configuring [`babel.cherryPick`](/docs/Configuration.md#cherrypick-string--arraystring) for these libraries to only bundle the modules you actually use. 71 | 72 | ### How can I copy non-JavaScript files when building a React component/library? 73 | 74 | Pass a [`--copy-files` flag](/docs/guides/ReactComponent.md#--copy-files) if you have other files which you want to copy to build directories, such as CSS and JSON files. 75 | 76 | ### How can I debug using VS Code when using nwb? 77 | 78 | Ensure you have the [Debugger for Chrome extension](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) installed and add the following configurations to `.vscode/launch.json`: 79 | 80 | ```json 81 | { 82 | "version": "0.2.0", 83 | "configurations": [ 84 | { 85 | "name": "Debug Dev Server", 86 | "request": "launch", 87 | "sourceMapPathOverrides": { 88 | "webpack:///src/*": "${webRoot}/*" 89 | }, 90 | "type": "chrome", 91 | "url": "http://localhost:3000", 92 | "webRoot": "${workspaceRoot}/src", 93 | }, 94 | { 95 | "name": "Debug Karma Tests", 96 | "request": "launch", 97 | "runtimeArgs": ["--headless"], 98 | "sourceMapPathOverrides": { 99 | "webpack:///src/*": "${workspaceRoot}/src/*", 100 | "webpack:///tests/*": "${workspaceRoot}/tests/*" 101 | }, 102 | "type": "chrome", 103 | "url": "http://localhost:9876/debug.html", 104 | } 105 | ] 106 | } 107 | ``` 108 | 109 | > **Note:** the above configuration assumes you're using the default host and port settings, and that the requested dev server port was available. 110 | 111 | After you've started the dev server with `npm start` or `nwb serve`, or started a watching test server with `npm run test:watch` or `nwb test --server`, you should be able to start debugging in VS Code by running a debugging configuration from the Debug panel or pressing F5. 112 | -------------------------------------------------------------------------------- /docs/Features.md: -------------------------------------------------------------------------------- 1 | ## Features 2 | 3 | - **A toolkit, not a boilerplate.** 4 | - Uses [Webpack 4](https://webpack.js.org/), [Babel 7](https://babeljs.io/) and [Karma 5](https://karma-runner.github.io/). 5 | - Provides tooling for [React](https://facebook.github.io/react/) apps and components, [Preact](https://preactjs.com/) apps, [Inferno](https://infernojs.org/) apps, and vanilla JS web apps and npm modules. 6 | - Use modern JavaScript features and JSX. 7 | - Use proposed JavaScript features now. 8 | - Import CSS (and font resources) and images to be managed by Webpack. 9 | - [Autoprefixed](https://github.com/postcss/autoprefixer#autoprefixer-) CSS, so you don't need to write browser prefixes; you can also configure your own [PostCSS](https://postcss.org/) plugins. 10 | - Plugin modules which add support for the [Sass](https://github.com/insin/nwb-sass), [Less](https://github.com/insin/nwb-less) and [Stylus](https://github.com/insin/nwb-stylus) stylesheet languages. 11 | 12 | **Development / DX:** 13 | 14 | - Quick development commands for React, Preact and Inferno which reduce the time from idea to running code. 15 | - Development server with Hot Module Replacement, and syntax error and React `render()` error overlays. 16 | - User-friendly reporting of build status and errors in the CLI. 17 | - Prompts to continue with a different port if the intended dev server port is not available. 18 | - Express middleware for serving the same development Webpack build from your own server. 19 | - **Experimental:** Automatically install dependencies from npm without restarting your development server by using an `--auto-install` flag. 20 | - **Experimental:** Write destructured ES module `import`s which transpile to cherry-picked imports for specific modules to keep your bundle size down. 21 | 22 | **Testing:** 23 | 24 | - Run unit tests with Karma in headless Chrome, using [Mocha](https://mochajs.org/) and [Expect](https://github.com/mjackson/expect#expect1x-documentation), without any configuration. 25 | - Flexible defaults allow tests to be organised using a number of naming schemes, with tests in a separate directory, co-located with the code, or both. 26 | - Code coverage reporting. 27 | - Project skeletons ready to run tests on Travis CI and post code coverage results to Coveralls/codecov.io. 28 | - Convenient to configure your preferred testing framework and other browsers instead of the defaults, with bundled support for launching tests in Chrome. 29 | 30 | **Production:** 31 | 32 | - Optimised Webpack build prepares JS, CSS and images for production, with deterministic filename hashes for long-term caching, and sourcemaps for debugging. 33 | - Production optimisations for React apps: hoisting static elements and removing `propTypes` 34 | - Automatic creation of a separate vendor bundle. 35 | - Flags to try a build which replaces React with Preact or Inferno via a compatibility layer, for a much smaller payload. 36 | 37 | **Publishing:** 38 | 39 | - Builds components and modules for publishing to npm with ES5 (including CommonJS export interop), ES modules and UMD builds. 40 | - Project skeletons include a package.json config `files` whitelist to avoid bloating your published package. 41 | - React component skeleton includes a demo app ready to develop and build using the same React app development setup. 42 | - React component `propTypes` are automatically wrapped with an environment check, for elimination from production builds. 43 | 44 | **Configuration:** 45 | 46 | - Use a single [configuration file](https://github.com/insin/nwb/blob/master/docs/Configuration.md#configuration) to customise Babel, Webpack, Karma and npm builds if you need to. 47 | - [Declarative config for Webpack rules and plugin settings](https://github.com/insin/nwb/blob/master/docs/Configuration.md#webpack-configuration) makes then easy to tweak. 48 | - Convenience configuration for commonly-used features like Webpack aliases and expression replacements. 49 | - Compatibility configuration for libraries which commonly cause Webpack issues, e.g. for bundling Moment.js with only specified locales. 50 | -------------------------------------------------------------------------------- /docs/ImplementingPlugins.md: -------------------------------------------------------------------------------- 1 | ## Implementing Plugins 2 | 3 | Plugins are implemented as npm packages which have names beginning with `'nwb-'`, which export a plugin configuration object. 4 | 5 | nwb will scan the current project's `package.json` for these modules, then import them and merge their configuration objects for use when generating configurations. 6 | 7 | ### CSS Preprocessor Plugins 8 | 9 | CSS preprocessor plugins must export a configuration object in the following format: 10 | 11 | ```js 12 | { 13 | cssPreprocessors: { 14 | 'preprocessor-id': { 15 | test: /\.ext$/, 16 | loader: 'absolute path to a webpack loader module.js', 17 | // Other rule config, e.g. default options 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | The preprocessor id is critical - this will be used to generate names for the Webpack rules and loaders created for the preprocessor, which users can use in their `nwb.config.js` to apply configuration. 24 | 25 | ---- 26 | 27 | As a concrete example, this is a complete implementation of a Sass preprocessor plugin: 28 | 29 | ```js 30 | module.exports = { 31 | cssPreprocessors: { 32 | sass: { 33 | test: /\.s[ac]ss$/, 34 | loader: require.resolve('sass-loader') 35 | } 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/Middleware.md: -------------------------------------------------------------------------------- 1 | ## Middleware 2 | 3 | nwb provides middleware for serving a hot reloading app from your own server. 4 | 5 | This gives you the same setup as if you'd run the `nwb serve` command, using your app's `nwb.config.js` file for configuration as usual if you have one. 6 | 7 | ### Express 4.x Middleware 8 | 9 | Middleware for Express 4.x for developing apps can be imported from `'nwb/express'`. 10 | 11 | See the [nwb-react-tutorial](https://github.com/insin/nwb-react-tutorial) project for an example of using it. 12 | 13 | #### API 14 | 15 | ##### `middleware(express, options: Object)` 16 | 17 | ###### `express` 18 | 19 | Your app's version of the Express module must be passed as the first argument. 20 | 21 | ###### `options` 22 | 23 | - `type` - the type of project being built, must be one of `react`, `preact`, `inferno` or `web`. 24 | 25 | If you don't provide a `type`, nwb will try to grab it from your `nwb.config.js` file (or a different file specified using the `config` option) instead. 26 | 27 | If you don't have a config file, you must provide a `type` or the middleware won't know what to do and will throw an error. 28 | 29 | - `config` - path to a config file *[default: `'nwb.config.js'`]* 30 | - `entry` - entry point for the app *[default: `'src/index.js'`]* 31 | - `hmr` - use [React Refresh Webpack Plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin/#react-refresh-webpack-plugin) to enable Fast Refresh for React components *[default: `true`]* 32 | - `install` - automatically install missing npm dependencies *[default: `false`]* 33 | - `reload` - reload the page if Hot Module Replacement is unsuccessful *[default: `false`]* 34 | 35 | #### Example 36 | 37 | Here's a minimal express server which serves up an app: 38 | 39 | ```js 40 | var express = require('express') 41 | 42 | var app = express() 43 | 44 | app.use(require('nwb/express')(express)) 45 | 46 | app.use(express.static('public')) 47 | 48 | app.listen(3000, function(err) { 49 | if (err) { 50 | console.error('error starting server:') 51 | console.error(err.stack) 52 | process.exit(1) 53 | } 54 | console.log('server listening at http://localhost:3000') 55 | }) 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/Plugins.md: -------------------------------------------------------------------------------- 1 | ## Plugins 2 | 3 | Plugin modules provide additional functionality - if you have one installed in your project, nwb will automatically find it when creating configuration and integrate the functionality it provides. 4 | 5 | ### CSS Preprocessor Plugins 6 | 7 | CSS preprocessors convert styles in alternative style languages to CSS, with the resulting CSS being passed through the standard nwb CSS pipeline. 8 | 9 | - [nwb-less](https://github.com/insin/nwb-less) - adds processing of `.less` files which use [Less syntax](http://lesscss.org/) with [less-loader](https://github.com/webpack-contrib/less-loader#readme). 10 | - [nwb-sass](https://github.com/insin/nwb-sass) - adds processing of `.scss` and `.sass` files which use [Sass syntax](http://sass-lang.com/) with [sass-loader](https://github.com/jtangelder/sass-loader#readme) 11 | - [nwb-stylus](https://github.com/insin/nwb-stylus) - adds processing of `.styl` files which use [Stylus syntax](http://stylus-lang.com/) with [stylus-loader](https://github.com/shama/stylus-loader#readme) 12 | 13 | #### Example: using the Sass plugin 14 | 15 | If you want to use Sass in your project, install nwb-sass... 16 | 17 | ``` 18 | npm install --save-dev nwb-sass 19 | ``` 20 | 21 | ...and you can now import `.scss` or `.sass` files from JavaScript modules: 22 | 23 | ```js 24 | import './styles.scss' 25 | ``` 26 | 27 | The sass plugin's unique id is `sass` - you can use this in [`webpack.rules` config](/docs/Configuration.md#rules-object) to provide configuration options for sass-loader: 28 | 29 | ```js 30 | module.exports = { 31 | webpack: { 32 | rules: { 33 | sass: { 34 | data: '@import "_variables"', 35 | includePaths: [path.resolve('src/styles')] 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 | ### Documentation 4 | 5 | - [Features](/docs/Features.md#features) 6 | - [Commands](/docs/Commands.md#commands) 7 | - [`react`](/docs/Commands.md#react) 8 | - [`nwb`](/docs/Commands.md#nwb) 9 | - [Configuration](/docs/Configuration.md#configuration) 10 | - [Configuration File](/docs/Configuration.md#configuration-file) 11 | - [Configuration Object](/docs/Configuration.md#configuration-object) 12 | - [Babel Configuration](/docs/Configuration.md#babel-configuration) 13 | - [Webpack Configuration](/docs/Configuration.md#webpack-configuration) 14 | - [Karma Configuration](/docs/Configuration.md#karma-configuration) 15 | - [npm Build Configuration](/docs/Configuration.md#npm-build-configuration) 16 | - [Stylesheets](/docs/Stylesheets.md#stylesheets) 17 | - [Testing](/docs/Testing.md#testing) 18 | - [Plugins](/docs/Plugins.md#plugins) 19 | - [Middleware](/docs/Middleware.md#middleware) 20 | - [Examples](/docs/Examples.md#examples) 21 | - [Frequently Asked Questions](/docs/FAQ.md#frequently-asked-questions) 22 | 23 | ### Guides 24 | 25 | - [Developing React Apps with nwb](/docs/guides/ReactApps.md#developing-react-apps-with-nwb) 26 | - [Developing React Components and Libraries with nwb](/docs/guides/ReactComponents.md#developing-react-components-and-libraries-with-nwb) 27 | - [Quick Development with nwb](/docs/guides/QuickDevelopment.md#quick-development-with-nwb) 28 | 29 | --- 30 | 31 | - [Versioning](/docs/Versioning.md#versioning) 32 | -------------------------------------------------------------------------------- /docs/Versioning.md: -------------------------------------------------------------------------------- 1 | ## Versioning 2 | 3 | Since [Semantic Versioning v2.0.0](http://semver.org/spec/v2.0.0.html) specifies... 4 | 5 | > Major version zero (`0.y.z`) is for initial development. Anything may change at any time. The public API should not be considered stable. 6 | 7 | ...you can *technically* follow both SemVer and [Sentimental Versioning](http://sentimentalversioning.org/) at the same time. 8 | 9 | This is what versions mean during nwb's initial development: 10 | 11 | - `0.y` versions are major-ish, anything may change - **always read the [CHANGES](/CHANGES.md) file or [GitHub release notes](https://github.com/insin/nwb/releases) to review what's changed before upgrading**. 12 | 13 | *Where possible*, any changes required to the nwb config file format will be backwards-compatible in the `0.y` version they're introduced in, with a deprecation warning when the old format is used. Support for the old format will usually be dropped in the next `0.y` release or two. 14 | 15 | - `0.y.z` versions are minor-ish, and may contain bug fixes, non-breaking changes, minor new features and non-breaking dependency changes. 16 | 17 | I will be pinning my own projects' nwb version range against these - e.g. `"nwb": "0.12.x"` - but **[if in doubt](https://medium.com/@kentcdodds/why-semver-ranges-are-literally-the-worst-817cdcb09277), pin your dependencies against an exact version**. 18 | 19 | > Version 1.0.0 defines the public API. The way in which the version number is incremented after this release is dependent on this public API and how it changes. 20 | -------------------------------------------------------------------------------- /docs/guides/README.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 | - [Developing React Apps with nwb](/docs/guides/ReactApps.md#developing-react-apps-with-nwb) 4 | - [Developing React Components and Libraries with nwb](/docs/guides/ReactComponents.md#developing-react-components-and-libraries-with-nwb) 5 | - [Quick Development with nwb](/docs/guides/QuickDevelopment.md#quick-development-with-nwb) 6 | -------------------------------------------------------------------------------- /docs/guides/resources/react-app-build-dist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-app-build-dist.png -------------------------------------------------------------------------------- /docs/guides/resources/react-app-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-app-build.png -------------------------------------------------------------------------------- /docs/guides/resources/react-app-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-app-default.png -------------------------------------------------------------------------------- /docs/guides/resources/react-app-test-coverage-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-app-test-coverage-html.png -------------------------------------------------------------------------------- /docs/guides/resources/react-app-test-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-app-test-coverage.png -------------------------------------------------------------------------------- /docs/guides/resources/react-component-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-build.png -------------------------------------------------------------------------------- /docs/guides/resources/react-component-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-demo.gif -------------------------------------------------------------------------------- /docs/guides/resources/react-component-serve-error-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-serve-error-browser.png -------------------------------------------------------------------------------- /docs/guides/resources/react-component-serve-error-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-serve-error-console.png -------------------------------------------------------------------------------- /docs/guides/resources/react-component-test-coverage-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-test-coverage-html.png -------------------------------------------------------------------------------- /docs/guides/resources/react-component-test-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-component-test-coverage.png -------------------------------------------------------------------------------- /docs/guides/resources/react-serve-app-error-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve-app-error-browser.png -------------------------------------------------------------------------------- /docs/guides/resources/react-serve-app-error-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve-app-error-console.png -------------------------------------------------------------------------------- /docs/guides/resources/react-serve-app-hmr-css.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve-app-hmr-css.gif -------------------------------------------------------------------------------- /docs/guides/resources/react-serve-app-hmr-js.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve-app-hmr-js.gif -------------------------------------------------------------------------------- /docs/guides/resources/react-serve-app-render-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve-app-render-error.png -------------------------------------------------------------------------------- /docs/guides/resources/react-serve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/docs/guides/resources/react-serve.png -------------------------------------------------------------------------------- /express.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/expressMiddleware') 2 | -------------------------------------------------------------------------------- /flow-typed/libdefs.js: -------------------------------------------------------------------------------- 1 | declare module '@pmmmwh/react-refresh-webpack-plugin' { declare module.exports: any; } 2 | declare module 'autoprefixer' { declare module.exports: any; } 3 | declare module 'case-sensitive-paths-webpack-plugin' { declare module.exports: any; } 4 | declare module 'chalk' { declare module.exports: any; } 5 | declare module 'copy-webpack-plugin' { declare module.exports: any; } 6 | declare module 'cross-spawn' { declare module.exports: any; } 7 | declare module 'fs-extra' { declare module.exports: any; } 8 | declare module 'html-webpack-plugin' { declare module.exports: any; } 9 | declare module 'mini-css-extract-plugin' { declare module.exports: any; } 10 | declare module '@insin/npm-install-webpack-plugin' { declare module.exports: any; } 11 | declare module 'css-minimizer-webpack-plugin' { declare module.exports: any; } 12 | declare module 'ora' { declare module.exports: any; } 13 | declare module 'resolve' { declare module.exports: any; } 14 | declare module 'terser-webpack-plugin' { declare module.exports: any; } 15 | declare module 'webpack' { declare module.exports: any; } 16 | declare module 'webpack-dev-middleware' { declare module.exports: any; } 17 | declare module 'webpack-hot-middleware' { declare module.exports: any; } 18 | declare module 'webpack-merge' { declare module.exports: any; } 19 | -------------------------------------------------------------------------------- /flow-typed/npm/debug_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 405987958aa5512d6259ff42e56f7ecb 2 | // flow-typed version: 94e9f7e0a4/debug_v2.x.x/flow_>=v0.28.x 3 | 4 | declare module 'debug' { 5 | declare type Debugger = { 6 | (...args: Array): void, 7 | (formatter: string, ...args: Array): void, 8 | (err: Error, ...args: Array): void, 9 | enabled: boolean, 10 | log: () => {}, 11 | namespace: string; 12 | }; 13 | 14 | declare module.exports: (namespace: string) => Debugger; 15 | 16 | declare var names: Array; 17 | declare var skips: Array; 18 | declare var colors: Array; 19 | 20 | declare function disable(): void; 21 | declare function enable(namespaces: string): void; 22 | declare function enabled(name: string): boolean; 23 | declare function humanize(): void; 24 | declare function useColors(): boolean; 25 | declare function log(): void; 26 | 27 | declare var formatters: { 28 | [formatter: string]: () => {} 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /flow-typed/npm/expect_v1.x.x.js: -------------------------------------------------------------------------------- 1 | declare type $npm$expect$ErrorMatcher = Class | Error | RegExp | string; 2 | 3 | declare type $npm$expect$Spy = Function & { 4 | (...args: any[]): any; 5 | andCall(f: () => R): R; 6 | andCallThrough(): any; 7 | andReturn(value: any): any; 8 | andThrow(error: any): any; 9 | restore(): any; 10 | reset(): any; 11 | calls: Array<{ 12 | context: any; 13 | arguments: Array; 14 | }>; 15 | }; 16 | 17 | declare type $npm$expect$ComparatorFunction = { 18 | (left: any, right: any): boolean; 19 | } 20 | declare type $npm$expect$HasKeyFunction = { 21 | (object: any, propertyName: string): boolean; 22 | } 23 | declare class $npm$expect$Expectation { 24 | toExist(message?: string): this; 25 | toNotExist(message?: string): this; 26 | toBe(expected: T, message?: string): this; 27 | toNotBe(expected: T, message?: string): this; 28 | toEqual(expected: any, message?: string): this; 29 | toNotEqual(expected: any, message?: string): this; 30 | toThrow(error?: $npm$expect$ErrorMatcher, message?: string): this; 31 | toNotThrow(error?: $npm$expect$ErrorMatcher, message?: string): this; 32 | toBeA(constructor: mixed, message?: string): this; 33 | toNotBeA(constructor: mixed, message?: string): this; 34 | toMatch(pattern: any, message?: string): this; 35 | toNotMatch(pattern: any, message?: string): this; 36 | 37 | toBeLessThan(n: T & number, message?: string): this; 38 | toBeLessThanOrEqualTo(n: T & number, message?: string): this; 39 | toBeGreaterThan(n: T & number, message?: string): this; 40 | toBeGreaterThanOrEqualTo(n: T & number, message?: string): this; 41 | 42 | toInclude(value: any, comparator?: $npm$expect$ComparatorFunction, message?: string): this; 43 | toNotInclude(value: any, comparator?: $npm$expect$ComparatorFunction, message?: string): this; 44 | toContain(value: any, comparator?: $npm$expect$ComparatorFunction, message?: string): this; 45 | toNotContain(value: any, comparator?: $npm$expect$ComparatorFunction, message?: string): this; 46 | toExclude(value: any, comparator?: $npm$expect$ComparatorFunction, message?: string): this; 47 | 48 | toIncludeKey(key: string, hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 49 | toNotIncludeKey(key: string, hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 50 | toContainKey(key: string, hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 51 | toNotContainKey(key: string, hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 52 | toExcludeKey(key: string, hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 53 | 54 | toIncludeKeys(keys: string[], hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 55 | toNotIncludeKeys(keys: string[], hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 56 | toContainsKeys(keys: string[], hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 57 | toNotContainKeys(keys: string[], hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 58 | toExcludeKeys(key: string[], hasKeyFn?: $npm$expect$HasKeyFunction, message?: string): this; 59 | 60 | toHaveBeenCalled(message?: string): this; 61 | toNotHaveBeenCalled(message?: string): this; 62 | toHaveBeenCalledWith(...args: any[]): this; 63 | 64 | } 65 | 66 | declare type $npm$expect$ExpectStatic = { 67 | (actual: mixed): Expectation; 68 | assert(cond: boolean, message: string, ...extra: any[]): void; 69 | extend(spec: Object): any; 70 | createSpy(): $npm$expect$Spy; 71 | isSpy(o: any): boolean; 72 | spyOn(o: any, propertyName: string): $npm$expect$Spy; 73 | restoreSpies(): void; 74 | } 75 | 76 | declare module 'expect' { 77 | 78 | declare function assert(cond: boolean, message: string, ...extra: any[]): void; 79 | 80 | declare function extend(spec: any): any; 81 | 82 | declare function createSpy(): $npm$expect$Spy; 83 | 84 | declare function isSpy(o: any): boolean; 85 | 86 | declare function spyOn(o: any, propertyName: string): $npm$expect$Spy; 87 | 88 | declare function restoreSpies(): void; 89 | 90 | declare module.exports: $npm$expect$ExpectStatic<$npm$expect$Expectation>; 91 | } 92 | -------------------------------------------------------------------------------- /flow-typed/npm/minimist_v1.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 50bc453586282fb18e63201750049659 2 | // flow-typed version: e6f7626e10/minimist_v1.x.x/flow_>=v0.28.x 3 | 4 | declare module 'minimist' { 5 | declare type minimistOptions = { 6 | string?: string | Array, 7 | boolean?: boolean | string | Array, 8 | alias?: { [arg: string]: string | Array }, 9 | default?: { [arg: string]: any }, 10 | stopEarly?: boolean, 11 | // TODO: Strings as keys don't work... 12 | // '--'? boolean, 13 | unknown?: (param: string) => boolean 14 | }; 15 | 16 | declare type minimistOutput = { 17 | _: Array, 18 | [flag: string]: string | boolean 19 | }; 20 | 21 | declare module.exports: (argv: Array, opts?: minimistOptions) => minimistOutput; 22 | } 23 | -------------------------------------------------------------------------------- /flow-typed/npm/mocha_v3.1.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6b82cf8c1da27b4f0fa7a58e5ed5babf 2 | // flow-typed version: edf70dde46/mocha_v3.1.x/flow_>=v0.22.x 3 | 4 | type TestFunction = ((done: () => void) => void | Promise); 5 | 6 | declare var describe : { 7 | (name:string, spec:() => void): void; 8 | only(description:string, spec:() => void): void; 9 | skip(description:string, spec:() => void): void; 10 | timeout(ms:number): void; 11 | }; 12 | 13 | declare var context : typeof describe; 14 | 15 | declare var it : { 16 | (name:string, spec?:TestFunction): void; 17 | only(description:string, spec:TestFunction): void; 18 | skip(description:string, spec:TestFunction): void; 19 | timeout(ms:number): void; 20 | }; 21 | 22 | declare function before(method : TestFunction):void; 23 | declare function beforeEach(method : TestFunction):void; 24 | declare function after(method : TestFunction):void; 25 | declare function afterEach(method : TestFunction):void; 26 | -------------------------------------------------------------------------------- /flow-typed/npm/run-series_v1.x.x.js: -------------------------------------------------------------------------------- 1 | declare module 'run-series' { 2 | declare module.exports: ( 3 | tasks: Array<((err: ?Error, result: ?any) => any) => any>, 4 | cb: (err: ?Error, results: any[]) => any 5 | ) => void; 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/npm/semver_v5.1.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: c5f918cd3de18b19a20558e6f3bbcc84 2 | // flow-typed version: cdd17a64e0/semver_v5.1.x/flow_>=v0.27.0 3 | 4 | // List of members taken from here: https://www.npmjs.com/package/semver/#functions 5 | // TODO support the `loose` parameter 6 | // TODO support SemVer instances as input parameters 7 | declare module 'semver' { 8 | declare type Release = 9 | 'major' | 10 | 'premajor' | 11 | 'minor' | 12 | 'preminor' | 13 | 'patch' | 14 | 'prepatch' | 15 | 'prerelease'; 16 | 17 | // The supported comparators are taken from the source here: 18 | // https://github.com/npm/node-semver/blob/8bd070b550db2646362c9883c8d008d32f66a234/semver.js#L623 19 | declare type Comparator = 20 | '===' | 21 | '!==' | 22 | '==' | 23 | '=' | 24 | '' | // Not sure why you would want this, but whatever. 25 | '!=' | 26 | '>' | 27 | '>=' | 28 | '<' | 29 | '<='; 30 | 31 | declare class SemVer { 32 | loose: ?boolean, 33 | raw: string, 34 | major: number, 35 | minor: number, 36 | patch: number, 37 | prerelease: Array, 38 | build: Array, 39 | version: string, 40 | } 41 | 42 | // Functions 43 | declare function valid(v: string): string | null; 44 | declare function inc(v: string, release: Release): string | null; 45 | declare function major(v: string): number; 46 | declare function minor(v: string): number; 47 | declare function patch(v: string): number; 48 | 49 | // Comparison 50 | declare function gt(v1: string, v2: string): boolean; 51 | declare function gte(v1: string, v2: string): boolean; 52 | declare function lt(v1: string, v2: string): boolean; 53 | declare function lte(v1: string, v2: string): boolean; 54 | declare function eq(v1: string, v2: string): boolean; 55 | declare function neq(v1: string, v2: string): boolean; 56 | declare function cmp(v1: string, comparator: Comparator, v2: string): boolean; 57 | declare function compare(v1: string, v2: string): -1 | 0 | 1; 58 | declare function rcompare(v1: string, v2: string): -1 | 0 | 1; 59 | declare function diff(v1: string, v2: string): ?Release; 60 | 61 | // Ranges 62 | declare function validRange(r: string): string | null; 63 | declare function satisfies(version: string, range: string): boolean; 64 | declare function maxSatisfying(versions: Array, range: string): string | null; 65 | declare function gtr(version: string, range: string): boolean; 66 | declare function ltr(version: string, range: string): boolean; 67 | declare function outside(version: string, range: string, hilo: '>' | '<'): boolean; 68 | 69 | // Not explicitly documented 70 | declare function parse(version: string): ?SemVer; 71 | 72 | declare class Range { 73 | set: Array>; 74 | 75 | constructor(range: string, loose?: boolean): Range; 76 | 77 | format(): string; 78 | test(version: string): boolean; 79 | toString(): string; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /package-changelog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates changelog Markdown for pinned npm packages in a package.json diff 3 | * 4 | * Usage: git diff package.json | node package-changelog.js 5 | */ 6 | 7 | let fs = require('fs') 8 | 9 | let changes = fs.readFileSync(0, 'utf-8') 10 | 11 | let re = /^(?[+-])\s*"(?[^"]+)"\s*:\s*"(?\d+\.\d+\.\d+)"/gm 12 | 13 | let deps = new Map() 14 | 15 | Array.from(changes.matchAll(re)).forEach(({groups}) => { 16 | if (!deps.has(groups.pkg)) { 17 | deps.set(groups.pkg, {}) 18 | } 19 | deps.get(groups.pkg)[groups.change] = groups.version 20 | }) 21 | 22 | let changelog = Array.from(deps.keys()) 23 | .sort() 24 | .map((pkg) => { 25 | let versions = deps.get(pkg) 26 | if (!versions.hasOwnProperty('-')) { 27 | return `- ${pkg}: v${versions['+']}` 28 | } 29 | return `- ${pkg}: v${versions['-']} → [v${versions['+']}]()` 30 | }) 31 | .join('\n') 32 | 33 | console.log(changelog) 34 | -------------------------------------------------------------------------------- /resources/auto-install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/resources/auto-install.gif -------------------------------------------------------------------------------- /resources/config.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | // inputs 3 | "nwb.config.js" -> "User Babel Config" 4 | "nwb.config.js" -> "User Webpack Config" 5 | "nwb.config.js" -> "User Karma Config" 6 | "nwb.config.js" -> "User Module Build Config" 7 | 8 | "User Babel Config" -> "User Webpack Config" [label="contributes to"] 9 | 10 | "User Module Build Config" -> "Command Config" [label="configures"] 11 | 12 | "User Babel Config" -> "Babel" [label="transpile src"] 13 | "User Webpack Config" -> "Default Webpack Loader Config" [label="tweaks"] 14 | "User Webpack Config" -> "Default Webpack Plugin Config" [label="tweaks"] 15 | "User Webpack Config" -> "Webpack Loaders" [label="adds extra"] 16 | "User Webpack Config" -> "Webpack Plugins" [label="adds extra"] 17 | "User Karma Config" -> "Default Karma Config" [label="tweaks"] 18 | "User Karma Config" -> "Karma Config" [label="tweaks"] 19 | 20 | "nwb-* CSS Preprocessor Plugins" -> "Default Webpack Loader Config" [label="contribute to"] 21 | 22 | // nwb 23 | "Command Config" -> "Default Webpack Loader Config" [label="configures"] 24 | "Command Config" -> "Default Webpack Plugin Config" [label="configures"] 25 | "Command Config" -> "Webpack Config" [label="adds to"] 26 | 27 | "Default Webpack Loader Config" -> "Webpack Loaders" [label="defines base"] 28 | "Default Webpack Plugin Config" -> "Webpack Plugins" [label="defines base"] 29 | 30 | "Webpack Loaders" -> "Webpack Config" [label="part of"] 31 | "Webpack Loaders" -> "Babel" [label="uses"] 32 | 33 | "Webpack Plugins" -> "Webpack Config" [label="part of"] 34 | 35 | "Webpack Config" -> "Webpack" [label="run dev server"] 36 | "Webpack Config" -> "Webpack" [label="bundle app"] 37 | "Webpack Config" -> "Webpack" [label="create UMD build"] 38 | "Webpack Config" -> "Karma Config" [label="part of"] 39 | 40 | "Default Karma Config" -> "Karma Config" [label="defines base"] 41 | "Default Karma Config" -> "Webpack Loaders" [label="adds extra"] 42 | 43 | "Karma Config" -> "Karma" [label="run tests"] 44 | } 45 | -------------------------------------------------------------------------------- /resources/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/resources/cover.jpg -------------------------------------------------------------------------------- /resources/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/resources/linux.png -------------------------------------------------------------------------------- /resources/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/resources/windows.png -------------------------------------------------------------------------------- /src/WebpackStatusPlugin.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk' 3 | 4 | import {clearConsole} from './utils' 5 | import {logErrorsAndWarnings} from './webpackUtils' 6 | 7 | import type {ErrBack} from './types' 8 | 9 | type StatusPluginOptions = { 10 | disableClearConsole?: boolean, 11 | quiet?: boolean, 12 | successMessage?: ?string, 13 | }; 14 | 15 | /** 16 | * Display build status for a Webpack watching build. 17 | */ 18 | export default class StatusPlugin { 19 | disableClearConsole: boolean; 20 | quiet: boolean; 21 | successMessage: ?string; 22 | isInitialBuild: boolean; 23 | 24 | constructor(options: StatusPluginOptions = {}) { 25 | let { 26 | disableClearConsole = false, 27 | quiet = false, 28 | successMessage = '', 29 | } = options 30 | 31 | this.disableClearConsole = disableClearConsole 32 | this.quiet = quiet 33 | this.successMessage = successMessage 34 | 35 | // We only want to display the "Starting..." message once 36 | this.isInitialBuild = true 37 | } 38 | 39 | apply(compiler: Object) { 40 | compiler.hooks.watchRun.tapAsync('StatusPlugin', this.watchRun) 41 | compiler.hooks.done.tap('StatusPlugin', this.done) 42 | } 43 | 44 | clearConsole() { 45 | if (!this.quiet && !this.disableClearConsole) { 46 | clearConsole() 47 | } 48 | } 49 | 50 | log(message: any) { 51 | if (!this.quiet) { 52 | console.log(message) 53 | } 54 | } 55 | 56 | watchRun = (compiler: Object, cb: ErrBack) => { 57 | this.clearConsole() 58 | if (this.isInitialBuild) { 59 | this.log(chalk.cyan('Starting Webpack compilation...')) 60 | this.isInitialBuild = false 61 | } 62 | else { 63 | this.log('Recompiling...') 64 | } 65 | cb() 66 | } 67 | 68 | done = (stats: Object) => { 69 | this.clearConsole() 70 | 71 | let hasErrors = stats.hasErrors() 72 | let hasWarnings = stats.hasWarnings() 73 | 74 | if (!hasErrors && !hasWarnings) { 75 | let time = stats.endTime - stats.startTime 76 | this.log(chalk.green(`Compiled successfully in ${time} ms.`)) 77 | } 78 | else { 79 | logErrorsAndWarnings(stats) 80 | if (hasErrors) return 81 | } 82 | 83 | if (this.successMessage) { 84 | this.log('') 85 | this.log(this.successMessage) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/appCommands.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import fs from 'fs' 3 | import path from 'path' 4 | 5 | import runSeries from 'run-series' 6 | import merge from 'webpack-merge' 7 | 8 | import cleanApp from './commands/clean-app' 9 | import {DEFAULT_BROWSERS_DEV, DEFAULT_BROWSERS_PROD} from './constants' 10 | import {directoryExists, install} from './utils' 11 | import webpackBuild from './webpackBuild' 12 | import webpackServer from './webpackServer' 13 | 14 | import type {ErrBack} from './types' 15 | 16 | type AppConfig = { 17 | getName: () => string, 18 | getBuildDependencies: () => string[], 19 | getBuildConfig: () => Object, 20 | getServeConfig: () => Object, 21 | }; 22 | 23 | const DEFAULT_HTML_PATH = 'src/index.html' 24 | 25 | /** 26 | * Create a build, installing any required dependencies first if they're not 27 | * resolvable. 28 | */ 29 | export function build(args: Object, appConfig: AppConfig, cb: ErrBack) { 30 | let dist = args._[2] || 'dist' 31 | 32 | let tasks = [ 33 | (cb) => cleanApp({_: ['clean-app', dist]}, cb), 34 | (cb) => webpackBuild( 35 | `${appConfig.getName()} app`, 36 | args, 37 | () => createBuildConfig(args, appConfig.getBuildConfig()), 38 | cb 39 | ), 40 | ] 41 | 42 | let buildDependencies = appConfig.getBuildDependencies() 43 | if (buildDependencies.length > 0) { 44 | tasks.unshift((cb) => install(buildDependencies, {check: true}, cb)) 45 | } 46 | 47 | runSeries(tasks, cb) 48 | } 49 | 50 | /** 51 | * Create default command config for building an app and merge any extra config 52 | * provided into it. 53 | */ 54 | export function createBuildConfig(args: Object, extra: Object = {}) { 55 | let entry = path.resolve(args._[1] || 'src/index.js') 56 | let dist = path.resolve(args._[2] || 'dist') 57 | 58 | let production = process.env.NODE_ENV === 'production' 59 | let filenamePattern = production ? '[name].[chunkhash:8].js' : '[name].js' 60 | 61 | let config: Object = { 62 | babel: { 63 | env: { 64 | useBuiltIns: 'entry', 65 | corejs: 3, 66 | exclude: ['transform-typeof-symbol'], 67 | }, 68 | targets: DEFAULT_BROWSERS_PROD, 69 | }, 70 | devtool: 'source-map', 71 | entry: { 72 | app: [entry], 73 | }, 74 | output: { 75 | filename: filenamePattern, 76 | chunkFilename: filenamePattern, 77 | path: dist, 78 | publicPath: '/', 79 | }, 80 | plugins: { 81 | html: args.html !== false && getDefaultHTMLConfig(), 82 | vendor: args.vendor !== false, 83 | }, 84 | } 85 | 86 | if (directoryExists('public')) { 87 | config.plugins.copy = [{ 88 | from: path.resolve('public'), 89 | to: dist, 90 | globOptions: {ignore: ['.gitkeep']} 91 | }] 92 | } 93 | 94 | return merge(config, extra) 95 | } 96 | 97 | /** 98 | * Create default command config for serving an app and merge any extra config 99 | * objects provided into it. 100 | */ 101 | export function createServeConfig(args: Object, ...extra: Object[]) { 102 | let entry = path.resolve(args._[1] || 'src/index.js') 103 | let dist = path.resolve(args._[2] || 'dist') 104 | 105 | let config: Object = { 106 | babel: { 107 | env: { 108 | useBuiltIns: 'entry', 109 | corejs: 3, 110 | exclude: ['transform-typeof-symbol'], 111 | }, 112 | targets: DEFAULT_BROWSERS_DEV, 113 | }, 114 | entry: [entry], 115 | output: { 116 | path: dist, 117 | filename: '[name].js', 118 | publicPath: '/', 119 | }, 120 | plugins: { 121 | html: getDefaultHTMLConfig(), 122 | }, 123 | } 124 | 125 | if (directoryExists('public')) { 126 | config.plugins.copy = [{ 127 | from: path.resolve('public'), 128 | to: dist, 129 | globOptions: {ignore: ['.gitkeep']} 130 | }] 131 | } 132 | 133 | return merge(config, ...extra) 134 | } 135 | 136 | /** 137 | * Create default config for html-webpack-plugin. 138 | */ 139 | export function getDefaultHTMLConfig(cwd: string = process.cwd()) { 140 | // Use the default HTML template path if it exists 141 | if (fs.existsSync(path.join(cwd, DEFAULT_HTML_PATH))) { 142 | return { 143 | template: DEFAULT_HTML_PATH, 144 | } 145 | } 146 | // Otherwise provide default variables for the internal template, in case we 147 | // fall back to it. 148 | return { 149 | lang: 'en', 150 | mountId: 'app', 151 | title: require(path.join(cwd, 'package.json')).name, 152 | } 153 | } 154 | 155 | /** 156 | * Run a development server. 157 | */ 158 | export function serve(args: Object, appConfig: AppConfig, cb: ErrBack) { 159 | webpackServer( 160 | args, 161 | () => createServeConfig(args, appConfig.getServeConfig()), 162 | cb 163 | ) 164 | } 165 | -------------------------------------------------------------------------------- /src/bin/nwb.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {red} from 'chalk' 4 | 5 | import cli from '../cli' 6 | import {ConfigValidationError, KarmaExitCodeError, UserError} from '../errors' 7 | 8 | function handleError(error) { 9 | if (error instanceof UserError) { 10 | console.error(red(error.message)) 11 | } 12 | else if (error instanceof ConfigValidationError) { 13 | error.report.log() 14 | } 15 | else if (error instanceof KarmaExitCodeError) { 16 | console.error(red(`Karma exit code was ${error.exitCode}`)) 17 | } 18 | else { 19 | console.error(red(`Error running command: ${error.message}`)) 20 | if (error.stack) { 21 | console.error(error.stack) 22 | } 23 | } 24 | process.exit(1) 25 | } 26 | 27 | try { 28 | cli(process.argv.slice(2), err => { 29 | if (err) handleError(err) 30 | process.exit(0) 31 | }) 32 | } 33 | catch (e) { 34 | handleError(e) 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/build-demo.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import runSeries from 'run-series' 4 | 5 | import {DEFAULT_BROWSERS_PROD} from '../constants' 6 | import {directoryExists} from '../utils' 7 | import webpackBuild from '../webpackBuild' 8 | import cleanDemo from './clean-demo' 9 | 10 | function getCommandConfig(args) { 11 | let pkg = require(path.resolve('package.json')) 12 | 13 | let dist = path.resolve('demo/dist') 14 | let production = process.env.NODE_ENV === 'production' 15 | let filenamePattern = production ? '[name].[chunkhash:8].js' : '[name].js' 16 | 17 | let config = { 18 | babel: { 19 | env: { 20 | useBuiltIns: 'entry', 21 | corejs: 3, 22 | exclude: ['transform-typeof-symbol'], 23 | }, 24 | presets: ['react'], 25 | targets: DEFAULT_BROWSERS_PROD, 26 | }, 27 | devtool: 'source-map', 28 | entry: { 29 | demo: [path.resolve('demo/src/index.js')], 30 | }, 31 | output: { 32 | filename: filenamePattern, 33 | chunkFilename: filenamePattern, 34 | path: dist, 35 | }, 36 | plugins: { 37 | html: { 38 | lang: 'en', 39 | mountId: 'demo', 40 | title: args.title || `${pkg.name} ${pkg.version} Demo`, 41 | }, 42 | // A vendor bundle can be explicitly enabled with a --vendor flag 43 | vendor: args.vendor, 44 | }, 45 | } 46 | 47 | if (directoryExists('demo/public')) { 48 | config.plugins.copy = [{from: path.resolve('demo/public'), to: dist}] 49 | } 50 | 51 | return config 52 | } 53 | 54 | /** 55 | * Build a module's demo app from demo/src/index.js. 56 | */ 57 | export default function buildDemo(args, cb) { 58 | runSeries([ 59 | (cb) => cleanDemo(args, cb), 60 | (cb) => webpackBuild('demo', args, getCommandConfig, cb), 61 | ], cb) 62 | } 63 | -------------------------------------------------------------------------------- /src/commands/build-inferno-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../appCommands' 3 | import infernoConfig from '../inferno' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build an Inferno app. 9 | */ 10 | export default function buildPreactApp(args: Object, cb: ErrBack) { 11 | build(args, infernoConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-inferno.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import infernoConfig from '../inferno' 3 | import {build} from '../quickCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a standalone Inferno app entry module, component or VNode. 9 | */ 10 | export default function buildInferno(args: Object, cb: ErrBack) { 11 | build(args, infernoConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-preact-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../appCommands' 3 | import preactConfig from '../preact' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a Preact app. 9 | */ 10 | export default function buildPreactApp(args: Object, cb: ErrBack) { 11 | build(args, preactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-preact.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import preactConfig from '../preact' 3 | import {build} from '../quickCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a standalone Preact entry module, component or element. 9 | */ 10 | export default function buildPreact(args: Object, cb: ErrBack) { 11 | build(args, preactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-react-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../appCommands' 3 | import reactConfig from '../react' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a React app. 9 | */ 10 | export default function buildReactApp(args: Object, cb: ErrBack) { 11 | build(args, reactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-react-component.js: -------------------------------------------------------------------------------- 1 | import runSeries from 'run-series' 2 | 3 | import moduleBuild from '../moduleBuild' 4 | import {directoryExists} from '../utils' 5 | import buildDemo from './build-demo' 6 | 7 | /** 8 | * Create a React component's CommonJS and ES modules and UMD builds, and build 9 | * its demo app if it has one. 10 | */ 11 | export default function buildModule(args, cb) { 12 | let config = { 13 | babel: { 14 | presets: ['react'], 15 | runtime: { 16 | helpers: false 17 | } 18 | } 19 | } 20 | 21 | // Disable removal of propTypes in production builds with --[keep-]proptypes 22 | if (args.proptypes !== true && args['keep-proptypes'] !== true) { 23 | // Wrap propTypes with an environment check in development builds 24 | config.babelDev = { 25 | removePropTypes: { 26 | mode: 'wrap', 27 | } 28 | } 29 | // Strip propTypes and prop-type imports from UMD production build 30 | config.babelProd = { 31 | removePropTypes: { 32 | removeImport: true, 33 | } 34 | } 35 | } 36 | 37 | let tasks = [(cb) => moduleBuild(args, config, cb)] 38 | // Disable demo build with --no-demo or --no-demo-build 39 | if (args.demo !== false && 40 | args['demo-build'] !== false && 41 | directoryExists('demo')) { 42 | tasks.push((cb) => buildDemo(args, cb)) 43 | } 44 | runSeries(tasks, cb) 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/build-react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../quickCommands' 3 | import reactConfig from '../react' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a standalone React app entry module, component or element. 9 | */ 10 | export default function buildReact(args: Object, cb: ErrBack) { 11 | build(args, reactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-web-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../appCommands' 3 | import webConfig from '../web' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a plain JS app. 9 | */ 10 | export default function buildWebApp(args: Object, cb: ErrBack) { 11 | build(args, webConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build-web-module.js: -------------------------------------------------------------------------------- 1 | import moduleBuild from '../moduleBuild' 2 | 3 | /** 4 | * Create a web module's ES5, ES modules and UMD builds. 5 | */ 6 | export default function buildModule(args, cb) { 7 | moduleBuild(args, { 8 | babel: { 9 | runtime: { 10 | helpers: false 11 | } 12 | } 13 | }, cb) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/build-web.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {build} from '../quickCommands' 3 | import webConfig from '../web' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a vanilla JavaScript app. 9 | */ 10 | export default function buildWE(args: Object, cb: ErrBack) { 11 | build(args, webConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/build.js: -------------------------------------------------------------------------------- 1 | import {getProjectType} from '../config' 2 | import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP, WEB_MODULE} from '../constants' 3 | import buildInfernoApp from './build-inferno-app' 4 | import buildPreactApp from './build-preact-app' 5 | import buildReactApp from './build-react-app' 6 | import buildReactComponent from './build-react-component' 7 | import buildWebApp from './build-web-app' 8 | import buildWebModule from './build-web-module' 9 | 10 | const BUILD_COMMANDS = { 11 | [INFERNO_APP]: buildInfernoApp, 12 | [PREACT_APP]: buildPreactApp, 13 | [REACT_APP]: buildReactApp, 14 | [REACT_COMPONENT]: buildReactComponent, 15 | [WEB_APP]: buildWebApp, 16 | [WEB_MODULE]: buildWebModule, 17 | } 18 | 19 | /** 20 | * Generic build command, invokes the appropriate project type-specific command. 21 | */ 22 | export default function build(args, cb) { 23 | let projectType 24 | try { 25 | projectType = getProjectType(args) 26 | } 27 | catch (e) { 28 | return cb(e) 29 | } 30 | 31 | BUILD_COMMANDS[projectType](args, cb) 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/check-config.js: -------------------------------------------------------------------------------- 1 | import {getPluginConfig, getUserConfig, UserConfigReport} from '../config' 2 | 3 | function getFullEnv(env) { 4 | if (env === 'dev') return 'development' 5 | if (env === 'prod') return 'production' 6 | return env 7 | } 8 | 9 | export default function checkConfig(args) { 10 | if (args.e || args.env) { 11 | process.env.NODE_ENV = getFullEnv(args.e || args.env) 12 | } 13 | let pluginConfig = getPluginConfig(args) 14 | try { 15 | getUserConfig( 16 | {_: [args.command || 'check-config'], config: args._[1]}, 17 | {check: true, pluginConfig, required: true} 18 | ) 19 | } 20 | catch (report) { 21 | if (!(report instanceof UserConfigReport)) throw report 22 | report.log() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/clean-app.js: -------------------------------------------------------------------------------- 1 | import {clean} from '../utils' 2 | 3 | export default function cleanApp(args, cb) { 4 | let dist = args._[1] || 'dist' 5 | clean('app', ['coverage', dist], cb) 6 | } 7 | -------------------------------------------------------------------------------- /src/commands/clean-demo.js: -------------------------------------------------------------------------------- 1 | import {clean} from '../utils' 2 | 3 | export default function cleanDemo(args, cb) { 4 | clean('demo', ['demo/dist'], cb) 5 | } 6 | -------------------------------------------------------------------------------- /src/commands/clean-module.js: -------------------------------------------------------------------------------- 1 | import {clean} from '../utils' 2 | 3 | export default function cleanModule(args, cb) { 4 | clean('module', ['coverage', 'es', 'lib', 'umd'], cb) 5 | } 6 | -------------------------------------------------------------------------------- /src/commands/clean.js: -------------------------------------------------------------------------------- 1 | import {getProjectType} from '../config' 2 | import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP, WEB_MODULE} from '../constants' 3 | import cleanApp from './clean-app' 4 | import cleanModule from './clean-module' 5 | 6 | const CLEAN_COMMANDS = { 7 | [INFERNO_APP]: cleanApp, 8 | [PREACT_APP]: cleanApp, 9 | [REACT_APP]: cleanApp, 10 | [REACT_COMPONENT]: cleanModule, 11 | [WEB_APP]: cleanApp, 12 | [WEB_MODULE]: cleanModule, 13 | } 14 | 15 | /** 16 | * Generic clean command, invokes the appropriate project type-specific command. 17 | */ 18 | export default function clean(args, cb) { 19 | let projectType 20 | try { 21 | projectType = getProjectType(args) 22 | } 23 | catch (e) { 24 | return cb(e) 25 | } 26 | 27 | CLEAN_COMMANDS[projectType](args, cb) 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/inferno.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {cyan as opt, green as cmd, red, yellow as req} from 'chalk' 3 | import parseArgs from 'minimist' 4 | 5 | import {CONFIG_FILE_NAME} from '../constants' 6 | import {ConfigValidationError, UserError} from '../errors' 7 | 8 | const COMMAND_MODULES = { 9 | build: 'build-inferno', 10 | run: 'serve-inferno', 11 | } 12 | 13 | function handleError(error) { 14 | if (error instanceof UserError) { 15 | console.error(red(error.message)) 16 | } 17 | else if (error instanceof ConfigValidationError) { 18 | error.report.log() 19 | } 20 | else { 21 | console.error(red(`Error running command: ${error.message}`)) 22 | if (error.stack) { 23 | console.error(error.stack) 24 | } 25 | } 26 | process.exit(1) 27 | } 28 | 29 | let args = parseArgs(process.argv.slice(3), { 30 | alias: { 31 | c: 'config', 32 | p: 'plugins', 33 | } 34 | }) 35 | 36 | let command = args._[0] 37 | 38 | if (!command || /^h(elp)?$/.test(command)) { 39 | console.log(`Usage: ${cmd('nwb inferno')} ${req('(run|build)')} ${opt('[options]')} 40 | 41 | Options: 42 | ${opt('-c, --config')} config file to use ${opt(`[default: ${CONFIG_FILE_NAME}]`)} 43 | ${opt('-p, --plugins')} a comma-separated list of nwb plugins to use 44 | 45 | Commands: 46 | ${cmd('nwb inferno run')} ${req('')} ${opt('[options]')} 47 | Serve an Inferno app or component module. 48 | 49 | Arguments: 50 | ${req('entry')} entry point for the app, or a component module 51 | 52 | Options: 53 | ${opt('--force')} don't shim rendering, use the given entry module directly 54 | ${opt('--install')} automatically install missing npm dependencies 55 | ${opt('--host')} hostname to bind the dev server to 56 | ${opt('--mount-id')} id for the
the app will render into ${opt('[default: app]')} 57 | ${opt('--no-fallback')} disable serving of the index page from any path 58 | ${opt('--port')} port to run the dev server on ${opt('[default: 3000]')} 59 | ${opt('--reload')} auto reload the page if hot reloading fails 60 | ${opt('--title')} contents for ${opt('[default: Inferno App]')} 61 | 62 | ${cmd('nwb inferno build')} ${req('<entry>')} ${opt('[dist_dir] [options]')} 63 | Create a static build for an Inferno app. 64 | 65 | Arguments: 66 | ${req('entry')} entry point for the app 67 | ${opt('dist_dir')} build output directory ${opt('[default: dist/]')} 68 | 69 | Options: 70 | ${opt('--force')} don't shim rendering, use the given entry module directly 71 | ${opt('--mount-id')} id for the <div> the app will render into ${opt('[default: app]')} 72 | ${opt('--title')} contents for <title> ${opt('[default: Inferno App]')} 73 | ${opt('--vendor')} create a 'vendor' bundle for node_modules/ modules 74 | `) 75 | process.exit(command ? 0 : 1) 76 | } 77 | 78 | if (!COMMAND_MODULES.hasOwnProperty(command)) { 79 | console.error(`${red('Unknown inferno command:')} ${req(command)}`) 80 | process.exit(1) 81 | } 82 | 83 | let commandModule = require(`./${COMMAND_MODULES[command]}`) 84 | 85 | try { 86 | commandModule(args, err => { 87 | if (err) handleError(err) 88 | }) 89 | } 90 | catch (e) { 91 | handleError(e) 92 | } 93 | -------------------------------------------------------------------------------- /src/commands/init.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import {PROJECT_TYPES} from '../constants' 4 | import createProject, {validateProjectType} from '../createProject' 5 | import {UserError} from '../errors' 6 | 7 | export default function init(args, cb) { 8 | if (args._.length === 1) { 9 | return cb(new UserError(`usage: nwb init [${Array.from(PROJECT_TYPES).join('|')}] [name]`)) 10 | } 11 | 12 | let projectType = args._[1] 13 | try { 14 | validateProjectType(projectType) 15 | } 16 | catch (e) { 17 | return cb(e) 18 | } 19 | 20 | let name = args._[2] 21 | if (!name) { 22 | name = path.basename(process.cwd()) 23 | } 24 | 25 | let initialVowel = /^[aeiou]/.test(projectType) 26 | console.log(`Initialising ${initialVowel ? 'an' : 'a'} ${projectType} project...`) 27 | createProject(args, projectType, name, process.cwd(), cb) 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/new.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import {PROJECT_TYPES} from '../constants' 4 | import createProject, {validateProjectType} from '../createProject' 5 | import {UserError} from '../errors' 6 | import {directoryExists} from '../utils' 7 | 8 | export default function new_(args, cb) { 9 | if (args._.length === 1) { 10 | return cb(new UserError(`usage: nwb new [${Array.from(PROJECT_TYPES).join('|')}] <name>`)) 11 | } 12 | 13 | let projectType = args._[1] 14 | try { 15 | validateProjectType(projectType) 16 | } 17 | catch (e) { 18 | return cb(e) 19 | } 20 | 21 | let name = args._[2] 22 | if (!name) { 23 | return cb(new UserError('A project name must be provided')) 24 | } 25 | if (directoryExists(name)) { 26 | return cb(new UserError(`A ${name}/ directory already exists`)) 27 | } 28 | 29 | let targetDir = path.resolve(name) 30 | let initialVowel = /^[aeiou]/.test(projectType) 31 | console.log(`Creating ${initialVowel ? 'an' : 'a'} ${projectType} project...`) 32 | createProject(args, projectType, name, targetDir, cb) 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/preact.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {cyan as opt, green as cmd, red, yellow as req} from 'chalk' 3 | import parseArgs from 'minimist' 4 | 5 | import {CONFIG_FILE_NAME} from '../constants' 6 | import {ConfigValidationError, UserError} from '../errors' 7 | 8 | const COMMAND_MODULES = { 9 | build: 'build-preact', 10 | run: 'serve-preact', 11 | } 12 | 13 | function handleError(error) { 14 | if (error instanceof UserError) { 15 | console.error(red(error.message)) 16 | } 17 | else if (error instanceof ConfigValidationError) { 18 | error.report.log() 19 | } 20 | else { 21 | console.error(red(`Error running command: ${error.message}`)) 22 | if (error.stack) { 23 | console.error(error.stack) 24 | } 25 | } 26 | process.exit(1) 27 | } 28 | 29 | let args = parseArgs(process.argv.slice(3), { 30 | alias: { 31 | c: 'config', 32 | p: 'plugins', 33 | } 34 | }) 35 | 36 | let command = args._[0] 37 | 38 | if (!command || /^h(elp)?$/.test(command)) { 39 | console.log(`Usage: ${cmd('nwb preact')} ${req('(run|build)')} ${opt('[options]')} 40 | 41 | Options: 42 | ${opt('-c, --config')} config file to use ${opt(`[default: ${CONFIG_FILE_NAME}]`)} 43 | ${opt('-p, --plugins')} a comma-separated list of nwb plugins to use 44 | 45 | Commands: 46 | ${cmd('nwb preact run')} ${req('<entry>')} ${opt('[options]')} 47 | Serve a Preact app or component module. 48 | 49 | Arguments: 50 | ${req('entry')} entry point for the app, or a component module 51 | 52 | Options: 53 | ${opt('--force')} don't shim rendering, use the given entry module directly 54 | ${opt('--install')} automatically install missing npm dependencies 55 | ${opt('--host')} hostname to bind the dev server to 56 | ${opt('--mount-id')} id for the <div> the app will render into ${opt('[default: app]')} 57 | ${opt('--no-fallback')} disable serving of the index page from any path 58 | ${opt('--port')} port to run the dev server on ${opt('[default: 3000]')} 59 | ${opt('--reload')} auto reload the page if hot reloading fails 60 | ${opt('--title')} contents for <title> ${opt('[default: Preact App]')} 61 | 62 | ${cmd('nwb preact build')} ${req('<entry>')} ${opt('[dist_dir] [options]')} 63 | Create a static build for a Preact app. 64 | 65 | Arguments: 66 | ${req('entry')} entry point for the app 67 | ${opt('dist_dir')} build output directory ${opt('[default: dist/]')} 68 | 69 | Options: 70 | ${opt('--force')} don't shim rendering, use the given entry module directly 71 | ${opt('--mount-id')} id for the <div> the app will render into ${opt('[default: app]')} 72 | ${opt('--title')} contents for <title> ${opt('[default: Preact App]')} 73 | ${opt('--vendor')} create a 'vendor' bundle for node_modules/ modules 74 | `) 75 | process.exit(command ? 0 : 1) 76 | } 77 | 78 | if (!COMMAND_MODULES.hasOwnProperty(command)) { 79 | console.error(`${red('Unknown preact command:')} ${req(command)}`) 80 | process.exit(1) 81 | } 82 | 83 | let commandModule = require(`./${COMMAND_MODULES[command]}`) 84 | 85 | try { 86 | commandModule(args, err => { 87 | if (err) handleError(err) 88 | }) 89 | } 90 | catch (e) { 91 | handleError(e) 92 | } 93 | -------------------------------------------------------------------------------- /src/commands/react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {cyan as opt, green as cmd, red, yellow as req} from 'chalk' 3 | import parseArgs from 'minimist' 4 | 5 | import {CONFIG_FILE_NAME} from '../constants' 6 | import {ConfigValidationError, UserError} from '../errors' 7 | 8 | const COMMAND_MODULES = { 9 | build: 'build-react', 10 | run: 'serve-react', 11 | } 12 | 13 | function handleError(error) { 14 | if (error instanceof UserError) { 15 | console.error(red(error.message)) 16 | } 17 | else if (error instanceof ConfigValidationError) { 18 | error.report.log() 19 | } 20 | else { 21 | console.error(red(`Error running command: ${error.message}`)) 22 | if (error.stack) { 23 | console.error(error.stack) 24 | } 25 | } 26 | process.exit(1) 27 | } 28 | 29 | let args = parseArgs(process.argv.slice(3), { 30 | alias: { 31 | c: 'config', 32 | p: 'plugins', 33 | } 34 | }) 35 | 36 | let command = args._[0] 37 | 38 | if (!command || /^h(elp)?$/.test(command)) { 39 | console.log(`Usage: ${cmd('nwb react')} ${req('(run|build)')} ${opt('[options]')} 40 | 41 | Options: 42 | ${opt('-c, --config')} config file to use ${opt(`[default: ${CONFIG_FILE_NAME}]`)} 43 | ${opt('-p, --plugins')} a comma-separated list of nwb plugins to use 44 | 45 | Commands: 46 | ${cmd('nwb react run')} ${req('<entry>')} ${opt('[options]')} 47 | Serve a React app or component module. 48 | 49 | Arguments: 50 | ${req('entry')} entry point for the app, or a component module 51 | 52 | Options: 53 | ${opt('--force')} don't shim rendering, use the given entry module directly 54 | ${opt('--install')} automatically install missing npm dependencies 55 | ${opt('--host')} hostname to bind the dev server to 56 | ${opt('--mount-id')} id for the <div> the app will render into ${opt('[default: app]')} 57 | ${opt('--no-fallback')} disable serving of the index page from any path 58 | ${opt('--no-hmr')} disable use of Fast Refresh for Hot Module Replacement 59 | ${opt('--port')} port to run the dev server on ${opt('[default: 3000]')} 60 | ${opt('--reload')} auto reload the page if hot reloading fails 61 | ${opt('--title')} contents for <title> ${opt('[default: React App]')} 62 | 63 | ${cmd('nwb react build')} ${req('<entry>')} ${opt('[dist_dir] [options]')} 64 | Create a static build for a React app. 65 | 66 | Arguments: 67 | ${req('entry')} entry point for the app 68 | ${opt('dist_dir')} build output directory ${opt('[default: dist/]')} 69 | 70 | Options: 71 | ${opt('--force')} don't shim rendering, use the given entry module directly 72 | ${opt('--mount-id')} id for the <div> the app will render into ${opt('[default: app]')} 73 | ${opt('--title')} contents for <title> ${opt('[default: React App]')} 74 | ${opt('--vendor')} create a 'vendor' bundle for node_modules/ modules 75 | 76 | React-compatible builds using other libraries: 77 | ${opt('--inferno[-compat]')} create an Inferno compatibility build 78 | ${opt('--preact[-compat]')} create a Preact compatibility build 79 | `) 80 | process.exit(command ? 0 : 1) 81 | } 82 | 83 | if (!COMMAND_MODULES.hasOwnProperty(command)) { 84 | console.error(`${red('Unknown react command:')} ${req(command)}`) 85 | process.exit(1) 86 | } 87 | 88 | let commandModule = require(`./${COMMAND_MODULES[command]}`) 89 | 90 | try { 91 | commandModule(args, err => { 92 | if (err) handleError(err) 93 | }) 94 | } 95 | catch (e) { 96 | handleError(e) 97 | } 98 | -------------------------------------------------------------------------------- /src/commands/serve-inferno-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import infernoConfig from '../inferno' 3 | import {serve} from '../appCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve an Inferno app. 9 | */ 10 | export default function serveInferno(args: Object, cb: ErrBack) { 11 | serve(args, infernoConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-inferno.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import infernoConfig from '../inferno' 3 | import {serve} from '../quickCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a standalone Inferno app entry module, component or VNode. 9 | */ 10 | export default function serveInferno(args: Object, cb: ErrBack) { 11 | serve(args, infernoConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-preact-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import preactConfig from '../preact' 3 | import {serve} from '../appCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve a Preact app. 9 | */ 10 | export default function servePreact(args: Object, cb: ErrBack) { 11 | serve(args, preactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-preact.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import preactConfig from '../preact' 3 | import {serve} from '../quickCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Build a standalone Preact app entry module, component or element. 9 | */ 10 | export default function servePreact(args: Object, cb: ErrBack) { 11 | serve(args, preactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-react-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {serve} from '../appCommands' 3 | import reactConfig from '../react' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve a React app. 9 | */ 10 | export default function serveReact(args: Object, cb: ErrBack) { 11 | serve(args, reactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-react-demo.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import {DEFAULT_BROWSERS_DEV} from '../constants' 4 | import {directoryExists} from '../utils' 5 | import webpackServer from '../webpackServer' 6 | 7 | /** 8 | * Serve a React demo app from demo/src/index.js. 9 | */ 10 | export default function serveReactDemo(args, cb) { 11 | let pkg = require(path.resolve('package.json')) 12 | 13 | let dist = path.resolve('demo/dist') 14 | 15 | let config = { 16 | babel: { 17 | env: { 18 | useBuiltIns: 'entry', 19 | corejs: 3, 20 | exclude: ['transform-typeof-symbol'], 21 | }, 22 | presets: ['react'], 23 | targets: DEFAULT_BROWSERS_DEV, 24 | }, 25 | entry: [path.resolve('demo/src/index.js')], 26 | output: { 27 | filename: 'demo.js', 28 | path: dist, 29 | publicPath: '/', 30 | }, 31 | plugins: { 32 | html: { 33 | lang: 'en', 34 | mountId: 'demo', 35 | title: `${pkg.name} ${pkg.version} Demo`, 36 | }, 37 | }, 38 | } 39 | 40 | if (args.hmr !== false) { 41 | config.babel.plugins = [require.resolve('react-refresh/babel')] 42 | config.plugins.reactRefresh = true 43 | } 44 | 45 | if (directoryExists('demo/public')) { 46 | config.plugins.copy = [{from: path.resolve('demo/public'), to: dist}] 47 | } 48 | 49 | webpackServer(args, config, cb) 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/serve-react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {serve} from '../quickCommands' 3 | import reactConfig from '../react' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve a standalone React app entry module, component or element. 9 | */ 10 | export default function serveReact(args: Object, cb: ErrBack) { 11 | serve(args, reactConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-web-app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import webConfig from '../web' 3 | import {serve} from '../appCommands' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve a plain JS app. 9 | */ 10 | export default function serveWebApp(args: Object, cb: ErrBack) { 11 | serve(args, webConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve-web.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {serve} from '../quickCommands' 3 | import webConfig from '../web' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | /** 8 | * Serve a standalone vanilla JavaScript app. 9 | */ 10 | export default function serveWeb(args: Object, cb: ErrBack) { 11 | serve(args, webConfig(args), cb) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/serve.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {getProjectType} from '../config' 3 | import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_APP} from '../constants' 4 | import {UserError} from '../errors' 5 | import serveInfernoApp from './serve-inferno-app' 6 | import servePreactApp from './serve-preact-app' 7 | import serveReactApp from './serve-react-app' 8 | import serveReactDemo from './serve-react-demo' 9 | import serveWebApp from './serve-web-app' 10 | 11 | import type {ErrBack} from '../types' 12 | 13 | const SERVE_COMMANDS = { 14 | [INFERNO_APP]: serveInfernoApp, 15 | [PREACT_APP]: servePreactApp, 16 | [REACT_APP]: serveReactApp, 17 | [REACT_COMPONENT]: serveReactDemo, 18 | [WEB_APP]: serveWebApp, 19 | } 20 | 21 | /** 22 | * Generic serve command, invokes the appropriate project type-specific command. 23 | */ 24 | export default function serve(args: Object, cb: ErrBack) { 25 | let projectType 26 | try { 27 | projectType = getProjectType(args) 28 | } 29 | catch (e) { 30 | return cb(e) 31 | } 32 | 33 | if (!SERVE_COMMANDS[projectType]) { 34 | return cb(new UserError(`Unable to serve anything for a ${projectType} project.`)) 35 | } 36 | 37 | SERVE_COMMANDS[projectType](args, cb) 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/test-inferno.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import infernoConfig from '../inferno' 3 | import karmaServer from '../karmaServer' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | export default function testInferno(args: Object, cb: ErrBack) { 8 | karmaServer(args, infernoConfig(args).getKarmaTestConfig(), cb) 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/test-preact.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import karmaServer from '../karmaServer' 3 | import preactConfig from '../preact' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | export default function testPreact(args: Object, cb: ErrBack) { 8 | karmaServer(args, preactConfig(args).getKarmaTestConfig(), cb) 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/test-react-component.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import merge from 'webpack-merge' 3 | 4 | import karmaServer from '../karmaServer' 5 | import reactConfig from '../react' 6 | 7 | import type {ErrBack} from '../types' 8 | 9 | export default function testReactComponent(args: Object, cb: ErrBack) { 10 | karmaServer(args, merge(reactConfig(args).getKarmaTestConfig(), {}), cb) 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/test-react.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import karmaServer from '../karmaServer' 3 | import reactConfig from '../react' 4 | 5 | import type {ErrBack} from '../types' 6 | 7 | export default function testReact(args: Object, cb: ErrBack) { 8 | karmaServer(args, reactConfig(args).getKarmaTestConfig(), cb) 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/test-web-module.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import karmaServer from '../karmaServer' 3 | 4 | import type {ErrBack} from '../types' 5 | 6 | export default function testWebModule(args: Object, cb: ErrBack) { 7 | karmaServer(args, {}, cb) 8 | } 9 | -------------------------------------------------------------------------------- /src/commands/test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {getProjectType} from '../config' 3 | import {INFERNO_APP, PREACT_APP, REACT_APP, REACT_COMPONENT, WEB_MODULE} from '../constants' 4 | import karmaServer from '../karmaServer' 5 | import testInferno from './test-inferno' 6 | import testPreact from './test-preact' 7 | import testReact from './test-react' 8 | import testReactComponent from './test-react-component' 9 | import testWebModule from './test-web-module' 10 | 11 | import type {ErrBack} from '../types' 12 | 13 | const TEST_COMMANDS = { 14 | [INFERNO_APP]: testInferno, 15 | [PREACT_APP]: testPreact, 16 | [REACT_APP]: testReact, 17 | [REACT_COMPONENT]: testReactComponent, 18 | [WEB_MODULE]: testWebModule, 19 | } 20 | 21 | /** 22 | * Generic test command, invokes the appropriate project type-specific command, 23 | * or runs with the default test config. 24 | */ 25 | export default function test(args: Object, cb: ErrBack) { 26 | let projectType 27 | try { 28 | projectType = getProjectType(args) 29 | } 30 | catch (e) { 31 | // pass 32 | } 33 | 34 | if (projectType && TEST_COMMANDS[projectType]) { 35 | TEST_COMMANDS[projectType](args, cb) 36 | } 37 | else { 38 | karmaServer(args, {}, cb) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/web.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {cyan as opt, green as cmd, red, yellow as req} from 'chalk' 3 | import parseArgs from 'minimist' 4 | 5 | import {CONFIG_FILE_NAME} from '../constants' 6 | import {ConfigValidationError, UserError} from '../errors' 7 | 8 | const COMMAND_MODULES = { 9 | build: 'build-web', 10 | run: 'serve-web', 11 | } 12 | 13 | function handleError(error) { 14 | if (error instanceof UserError) { 15 | console.error(red(error.message)) 16 | } 17 | else if (error instanceof ConfigValidationError) { 18 | error.report.log() 19 | } 20 | else { 21 | console.error(red(`Error running command: ${error.message}`)) 22 | if (error.stack) { 23 | console.error(error.stack) 24 | } 25 | } 26 | process.exit(1) 27 | } 28 | 29 | let args = parseArgs(process.argv.slice(3), { 30 | alias: { 31 | c: 'config', 32 | p: 'plugins', 33 | } 34 | }) 35 | 36 | let command = args._[0] 37 | 38 | if (!command || /^h(elp)?$/.test(command)) { 39 | console.log(`Usage: ${cmd('nwb web')} ${req('(run|build)')} ${opt('[options]')} 40 | 41 | Options: 42 | ${opt('-c, --config')} config file to use ${opt(`[default: ${CONFIG_FILE_NAME}]`)} 43 | ${opt('-p, --plugins')} a comma-separated list of nwb plugins to use 44 | 45 | Commands: 46 | ${cmd('nwb web run')} ${req('<entry>')} ${opt('[options]')} 47 | Serve a vanilla JavaScript app. 48 | 49 | Arguments: 50 | ${req('entry')} entry point for the app 51 | 52 | Options: 53 | ${opt('--install')} automatically install missing npm dependencies 54 | ${opt('--host')} hostname to bind the dev server to 55 | ${opt('--mount-id')} id for the <div> in the default HTML ${opt('[default: app]')} 56 | ${opt('--no-fallback')} disable serving of the index page from any path 57 | ${opt('--no-reload')} disable auto reloading on changes 58 | ${opt('--port')} port to run the dev server on ${opt('[default: 3000]')} 59 | ${opt('--title')} contents for <title> ${opt('[default: Web App]')} 60 | 61 | ${cmd('nwb preact build')} ${req('<entry>')} ${opt('[dist_dir] [options]')} 62 | Create a static build for a vanilla JavaScript app. 63 | 64 | Arguments: 65 | ${req('entry')} entry point for the app 66 | ${opt('dist_dir')} build output directory ${opt('[default: dist/]')} 67 | 68 | Options: 69 | ${opt('--mount-id')} id for the <div> in the default HTML ${opt('[default: app]')} 70 | ${opt('--title')} contents for <title> ${opt('[default: Web App]')} 71 | ${opt('--vendor')} create a 'vendor' bundle for node_modules/ modules 72 | `) 73 | process.exit(command ? 0 : 1) 74 | } 75 | 76 | if (!COMMAND_MODULES.hasOwnProperty(command)) { 77 | console.error(`${red('Unknown web command:')} ${req(command)}`) 78 | process.exit(1) 79 | } 80 | 81 | let commandModule = require(`./${COMMAND_MODULES[command]}`) 82 | 83 | try { 84 | commandModule(args, err => { 85 | if (err) handleError(err) 86 | }) 87 | } 88 | catch (e) { 89 | handleError(e) 90 | } 91 | -------------------------------------------------------------------------------- /src/config/UserConfigReport.js: -------------------------------------------------------------------------------- 1 | import util from 'util' 2 | 3 | import chalk from 'chalk' 4 | import figures from 'figures' 5 | 6 | import {padLines, pluralise as s} from '../utils' 7 | 8 | export default class UserConfigReport { 9 | constructor({configFileExists, configPath} = {}) { 10 | this.configFileExists = configFileExists 11 | this.configPath = configPath 12 | this.deprecations = [] 13 | this.errors = [] 14 | this.hints = [] 15 | this.hasArgumentOverrides = false 16 | } 17 | 18 | deprecated(path, ...messages) { 19 | this.deprecations.push({path, messages}) 20 | } 21 | 22 | error(path, value, ...messages) { 23 | this.errors.push({path, value, message: messages.join('\n')}) 24 | } 25 | 26 | hasErrors() { 27 | return this.errors.length > 0 28 | } 29 | 30 | hasSomethingToReport() { 31 | return this.errors.length + this.deprecations.length + this.hints.length > 0 32 | } 33 | 34 | hint(path, ...messages) { 35 | this.hints.push({path, messages}) 36 | } 37 | 38 | getConfigSource() { 39 | if (this.configFileExists) { 40 | let description = this.configPath 41 | if (this.hasArgumentOverrides) { 42 | description += ' (with CLI argument overrides)' 43 | } 44 | return description 45 | } 46 | else if (this.hasArgumentOverrides) { 47 | return 'config via CLI arguments' 48 | } 49 | return 'funsies' 50 | } 51 | 52 | getReport() { 53 | let report = [] 54 | 55 | report.push(chalk.underline(`nwb config report for ${this.getConfigSource()}`)) 56 | report.push('') 57 | 58 | if (!this.hasSomethingToReport()) { 59 | report.push(chalk.green(`${figures.tick} Nothing to report!`)) 60 | return report.join('\n') 61 | } 62 | 63 | if (this.errors.length) { 64 | let count = this.errors.length > 1 ? `${this.errors.length} ` : '' 65 | report.push(chalk.red.underline(`${count}Error${s(this.errors.length)}`)) 66 | report.push('') 67 | } 68 | this.errors.forEach(({path, value, message}) => { 69 | report.push(`${chalk.red(`${figures.cross} ${path}`)}${value ? ` ${chalk.cyan('=')} ${util.inspect(value)}` : ''}`) 70 | report.push(padLines(message)) 71 | report.push('') 72 | }) 73 | 74 | if (this.deprecations.length) { 75 | let count = this.deprecations.length > 1 ? `${this.deprecations.length} ` : '' 76 | report.push(chalk.yellow.underline(`${count}Deprecation Warning${s(this.deprecations.length)}`)) 77 | report.push('') 78 | } 79 | this.deprecations.forEach(({path, messages}) => { 80 | report.push(chalk.yellow(`${figures.warning} ${path}`)) 81 | messages.forEach(message => { 82 | report.push(` ${message}`) 83 | }) 84 | report.push('') 85 | }) 86 | 87 | if (this.hints.length) { 88 | let count = this.hints.length > 1 ? `${this.hints.length} ` : '' 89 | report.push(chalk.cyan.underline(`${count}Hint${s(this.hints.length)}`)) 90 | report.push('') 91 | } 92 | this.hints.forEach(({path, messages}) => { 93 | report.push(chalk.cyan(`${figures.info} ${path}`)) 94 | messages.forEach(message => { 95 | report.push(` ${message}`) 96 | }) 97 | report.push('') 98 | }) 99 | 100 | return report.join('\n') 101 | } 102 | 103 | log() { 104 | console.log(this.getReport()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export {getPluginConfig} from './plugin' 2 | export {getProjectType, getUserConfig} from './user' 3 | export {default as UserConfigReport} from './UserConfigReport' 4 | -------------------------------------------------------------------------------- /src/config/karma.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | import chalk from 'chalk' 4 | 5 | import {pluralise as s, typeOf} from '../utils' 6 | 7 | export function processKarmaConfig({report, userConfig}) { 8 | let { 9 | browsers, 10 | excludeFromCoverage, 11 | frameworks, 12 | plugins, 13 | reporters, 14 | testContext, 15 | testFiles, 16 | extra, 17 | config, 18 | ...unexpectedConfig 19 | } = userConfig.karma 20 | 21 | let unexpectedProps = Object.keys(unexpectedConfig) 22 | if (unexpectedProps.length > 0) { 23 | report.error( 24 | 'karma', 25 | unexpectedProps.join(', '), 26 | `Unexpected prop${s(unexpectedProps.length)} in ${chalk.cyan('karma')} config - ` + 27 | 'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#karma-configuration for supported config.' + 28 | `If you were trying to add extra Karma config, try putting it in ${chalk.cyan('karma.extra')} instead` 29 | ) 30 | } 31 | 32 | // browsers 33 | if ('browsers' in userConfig.karma) { 34 | if (typeOf(browsers) !== 'array') { 35 | report.error( 36 | 'karma.browsers', 37 | browsers, 38 | `Must be an ${chalk.cyan('Array')}` 39 | ) 40 | } 41 | } 42 | 43 | // excludeFromCoverage 44 | if ('excludeFromCoverage' in userConfig.karma) { 45 | if (typeOf(excludeFromCoverage) === 'string') { 46 | userConfig.karma.excludeFromCoverage = [excludeFromCoverage] 47 | } 48 | else if (typeOf(excludeFromCoverage) !== 'array') { 49 | report.error( 50 | 'karma.excludeFromCoverage', 51 | excludeFromCoverage, 52 | `Must be a ${chalk.cyan('String')} or an ${chalk.cyan('Array')}` 53 | ) 54 | } 55 | } 56 | 57 | // frameworks 58 | if ('frameworks' in userConfig.karma) { 59 | if (typeOf(frameworks) !== 'array') { 60 | report.error( 61 | 'karma.frameworks', 62 | frameworks, 63 | `Must be an ${chalk.cyan('Array')}` 64 | ) 65 | } 66 | } 67 | 68 | // plugins 69 | if ('plugins' in userConfig.karma) { 70 | if (typeOf(plugins) !== 'array') { 71 | report.error( 72 | 'karma.plugins', 73 | plugins, 74 | `Must be an ${chalk.cyan('Array')}` 75 | ) 76 | } 77 | } 78 | 79 | // reporters 80 | if ('reporters' in userConfig.karma) { 81 | if (typeOf(reporters) !== 'array') { 82 | report.error( 83 | 'karma.reporters', 84 | reporters, 85 | `Must be an ${chalk.cyan('Array')}` 86 | ) 87 | } 88 | } 89 | 90 | // testContext 91 | if ('testContext' in userConfig.karma) { 92 | if (typeOf(testContext) !== 'string') { 93 | report.error( 94 | 'karma.testContext', 95 | testContext, 96 | `Must be a ${chalk.cyan('String')}` 97 | ) 98 | } 99 | else if (!fs.existsSync(testContext)) { 100 | report.error( 101 | 'karma.testContext', 102 | testContext, 103 | `The specified test context module does not exist` 104 | ) 105 | } 106 | } 107 | 108 | // testFiles 109 | if ('testFiles' in userConfig.karma) { 110 | if (typeOf(testFiles) === 'string') { 111 | userConfig.karma.testFiles = [testFiles] 112 | } 113 | else if (typeOf(testFiles) !== 'array') { 114 | report.error( 115 | 'karma.testFiles', 116 | testFiles, 117 | `Must be a ${chalk.cyan('String')} or an ${chalk.cyan('Array')}` 118 | ) 119 | } 120 | } 121 | 122 | // extra 123 | if ('extra' in userConfig.karma) { 124 | if (typeOf(extra) !== 'object') { 125 | report.error( 126 | 'karma.extra', 127 | `type: ${typeOf(extra)}`, 128 | `Must be an ${chalk.cyan('Object')}` 129 | ) 130 | } 131 | } 132 | 133 | // config 134 | if ('config' in userConfig.karma && typeOf(config) !== 'function') { 135 | report.error( 136 | `karma.config`, 137 | `type: ${typeOf(config)}`, 138 | `Must be a ${chalk.cyan('Function')}` 139 | ) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/config/npm.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | 3 | import {pluralise as s, typeOf} from '../utils' 4 | 5 | export function processNpmBuildConfig({report, userConfig}) { 6 | let { 7 | cjs, 8 | esModules, 9 | umd, 10 | ...unexpectedConfig 11 | } = userConfig.npm 12 | 13 | let unexpectedProps = Object.keys(unexpectedConfig) 14 | if (unexpectedProps.length > 0) { 15 | report.error( 16 | 'npm', 17 | unexpectedProps.join(', '), 18 | `Unexpected prop${s(unexpectedProps.length)} in ${chalk.cyan('babel')} config - ` + 19 | 'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#npm-build-configuration for supported config' 20 | ) 21 | } 22 | 23 | // cjs 24 | if ('cjs' in userConfig.npm) { 25 | if (typeOf(cjs) !== 'boolean') { 26 | report.error( 27 | 'npm.cjs', 28 | cjs, 29 | `Must be ${chalk.cyan('Boolean')}` 30 | ) 31 | } 32 | } 33 | 34 | // esModules 35 | if ('esModules' in userConfig.npm) { 36 | if (typeOf(esModules) !== 'boolean') { 37 | report.error( 38 | 'npm.esModules', 39 | esModules, 40 | `Must be ${chalk.cyan('Boolean')}` 41 | ) 42 | } 43 | } 44 | 45 | // umd 46 | if ('umd' in userConfig.npm) { 47 | if (umd === false) { 48 | // ok 49 | } 50 | else if (typeOf(umd) === 'string') { 51 | userConfig.npm.umd = {global: umd} 52 | } 53 | else if (typeOf(umd) !== 'object') { 54 | report.error( 55 | 'npm.umd', 56 | umd, 57 | `Must be a ${chalk.cyan('String')} (for ${chalk.cyan('global')} ` + 58 | `config only)}, an ${chalk.cyan('Object')} (for any UMD build options) ` + 59 | `or ${chalk.cyan('false')} (to explicitly disable the UMD build)` 60 | ) 61 | } 62 | else { 63 | let { 64 | entry, 65 | global: umdGlobal, 66 | externals, 67 | ...unexpectedConfig 68 | } = umd 69 | 70 | let unexpectedProps = Object.keys(unexpectedConfig) 71 | if (unexpectedProps.length > 0) { 72 | report.error( 73 | 'npm.umd', 74 | unexpectedProps.join(', '), 75 | `Unexpected prop${s(unexpectedProps.length)} in ${chalk.cyan('npm.umd')} config - ` + 76 | 'see https://github.com/insin/nwb/blob/master/docs/Configuration.md#umd-string--object for supported config' 77 | ) 78 | } 79 | 80 | if ('entry' in umd && typeOf(entry) !== 'string') { 81 | report.error( 82 | 'npm.umd.entry', 83 | entry, 84 | `Must be a ${chalk.cyan('String')}` 85 | ) 86 | } 87 | 88 | if ('global' in umd && typeOf(umdGlobal) !== 'string') { 89 | report.error( 90 | 'npm.umd.global', 91 | umdGlobal, 92 | `Must be a ${chalk.cyan('String')}` 93 | ) 94 | } 95 | 96 | if ('externals' in umd && typeOf(externals) !== 'object') { 97 | report.error( 98 | 'npm.umd.externals', 99 | externals, 100 | `Must be an ${chalk.cyan('Object')}` 101 | ) 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/config/plugin.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import resolve from 'resolve' 4 | import merge from 'webpack-merge' 5 | 6 | import debug from '../debug' 7 | import {deepToString, getArgsPlugins, unique} from '../utils' 8 | 9 | function getPackagePlugins(cwd) { 10 | let pkg = require(path.join(cwd, 'package.json')) 11 | return [ 12 | ...Object.keys(pkg.dependencies || {}), 13 | ...Object.keys(pkg.devDependencies || {}), 14 | ].filter(dep => /^nwb-/.test(dep)) 15 | } 16 | 17 | /** 18 | * Look for nwb-* plugin dependencies in package.json and plugins specified as 19 | * arguments when supported, import them and merge the plugin config objects 20 | * they export. 21 | */ 22 | export function getPluginConfig(args = {}, {cwd = process.cwd()} = {}) { 23 | let plugins = [] 24 | 25 | try { 26 | let pkgPlugins = plugins.concat(getPackagePlugins(cwd)) 27 | debug('%s nwb-* dependencies in package.json', pkgPlugins.length) 28 | plugins = plugins.concat(pkgPlugins) 29 | } 30 | catch (e) { 31 | // pass 32 | } 33 | 34 | let argsPlugins = getArgsPlugins(args) 35 | if (argsPlugins.length !== 0) { 36 | debug('%s plugins in arguments', argsPlugins.length) 37 | plugins = plugins.concat(argsPlugins) 38 | } 39 | 40 | if (plugins.length === 0) { 41 | return {} 42 | } 43 | 44 | plugins = unique(plugins) 45 | debug('nwb plugins: %o', plugins) 46 | 47 | let pluginConfig = {} 48 | plugins.forEach(plugin => { 49 | let pluginModule = require(resolve.sync(plugin, {basedir: cwd})) 50 | pluginConfig = merge(pluginConfig, pluginModule) 51 | }) 52 | 53 | debug('plugin config: %s', deepToString(pluginConfig)) 54 | 55 | return pluginConfig 56 | } 57 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const CONFIG_FILE_NAME = 'nwb.config.js' 2 | 3 | export const DEFAULT_PORT = process.env.PORT || 3000 4 | 5 | export const INFERNO_APP = 'inferno-app' 6 | export const PREACT_APP = 'preact-app' 7 | export const REACT_APP = 'react-app' 8 | export const REACT_COMPONENT = 'react-component' 9 | export const WEB_APP = 'web-app' 10 | export const WEB_MODULE = 'web-module' 11 | 12 | export const PROJECT_TYPES = new Set([ 13 | INFERNO_APP, 14 | PREACT_APP, 15 | REACT_APP, 16 | REACT_COMPONENT, 17 | WEB_APP, 18 | WEB_MODULE, 19 | ]) 20 | 21 | export const DEFAULT_BROWSERS_DEV = 'last 1 chrome version, last 1 firefox version, last 1 safari version' 22 | export const DEFAULT_BROWSERS_PROD = '>0.2%, not dead, not op_mini all' 23 | -------------------------------------------------------------------------------- /src/createServerWebpackConfig.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {getPluginConfig, getUserConfig} from './config' 3 | import createWebpackConfig from './createWebpackConfig' 4 | 5 | import type {ServerConfig} from './types' 6 | 7 | /** 8 | * Create Webpack entry config for the client which will subscribe to Hot Module 9 | * Replacement updates. 10 | */ 11 | function getHMRClientEntries(args: Object, serverConfig: ?ServerConfig): string[] { 12 | // null config indicates we're creating config for use in Express middleware, 13 | // where the server config is out of our hands and we're using 14 | // webpack-hot-middleware for HMR. 15 | if (serverConfig == null) { 16 | let hotMiddlewareOptions = args.reload ? '?reload=true' : '' 17 | return [ 18 | // Polyfill EventSource for IE11, as webpack-hot-middleware/client uses it 19 | require.resolve('eventsource-polyfill'), 20 | require.resolve('webpack-hot-middleware/client') + hotMiddlewareOptions, 21 | ] 22 | } 23 | // Otherwise, we're using webpack-dev-server's client 24 | let hmrURL = '/' 25 | // Set full HMR URL if the user customised it (#279) 26 | if (args.host || args.port) { 27 | hmrURL = `http://${serverConfig.host || 'localhost'}:${String(serverConfig.port)}/` 28 | } 29 | 30 | return [ 31 | require.resolve('webpack-dev-server/client') + `?${hmrURL}`, 32 | require.resolve(`webpack/hot/${args.reload ? '' : 'only-'}dev-server`), 33 | ] 34 | } 35 | 36 | /** 37 | * Creates Webpack config for serving a watch build with Hot Module Replacement. 38 | */ 39 | export default function createServerWebpackConfig( 40 | args: Object, 41 | commandConfig: Object, 42 | serverConfig: ?ServerConfig, 43 | ) { 44 | let pluginConfig = getPluginConfig(args) 45 | let userConfig = getUserConfig(args, {pluginConfig}) 46 | let {entry, plugins = {}, ...otherCommandConfig} = commandConfig 47 | 48 | if (args['auto-install'] || args.install) { 49 | plugins.autoInstall = true 50 | } 51 | 52 | return createWebpackConfig({ 53 | server: true, 54 | devtool: 'cheap-module-source-map', 55 | entry: getHMRClientEntries(args, serverConfig).concat(entry), 56 | plugins, 57 | ...otherCommandConfig, 58 | }, pluginConfig, userConfig) 59 | } 60 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug' 2 | 3 | export default debug('nwb') 4 | -------------------------------------------------------------------------------- /src/devServer.js: -------------------------------------------------------------------------------- 1 | import opn from 'open' 2 | import webpack from 'webpack' 3 | import WebpackDevServer from 'webpack-dev-server' 4 | import merge from 'webpack-merge' 5 | 6 | import debug from './debug' 7 | import {deepToString, typeOf} from './utils' 8 | 9 | /** 10 | * Use Webpack Dev Server to build and serve assets using Webpack's watch mode, 11 | * hot reload changes in the browser and display compile error overlays. 12 | * 13 | * Static content is handled by CopyPlugin. 14 | */ 15 | export default function devServer(webpackConfig, serverConfig, url, cb) { 16 | let compiler = webpack(webpackConfig) 17 | 18 | let {host, open, port, ...otherServerConfig} = serverConfig 19 | 20 | let webpackDevServerOptions = merge({ 21 | headers: { 22 | 'Access-Control-Allow-Origin': '*' 23 | }, 24 | historyApiFallback: true, 25 | hot: true, 26 | overlay: true, 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true, 29 | }, otherServerConfig) 30 | 31 | debug('webpack dev server options: %s', deepToString(webpackDevServerOptions)) 32 | 33 | let server = new WebpackDevServer(compiler, webpackDevServerOptions) 34 | 35 | // XXX Temporarily replace console.info() to prevent WDS startup logging which 36 | // is explicitly done at the info level when the quiet option is set. 37 | let info = console.info 38 | console.info = () => {} 39 | server.listen(port, host, (err) => { 40 | console.info = info 41 | if (err) return cb(err) 42 | if (open) { 43 | // --open 44 | if (typeOf(open) === 'boolean') opn(url, {url: true}) 45 | // --open=firefox 46 | else opn(url, {app: open, url: true}) 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * An error related to user input or configuration, or anything else the user is 5 | * responsible for and can fix. 6 | */ 7 | export class UserError extends Error {} 8 | 9 | export class KarmaExitCodeError { 10 | exitCode: number; 11 | constructor(exitCode: number) { 12 | this.exitCode = exitCode 13 | } 14 | } 15 | 16 | export class ConfigValidationError { 17 | report: Object; 18 | constructor(report: Object) { 19 | this.report = report 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/expressMiddleware.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import assert from 'assert' 3 | 4 | import webpack from 'webpack' 5 | 6 | import {createServeConfig} from './appCommands' 7 | import {getProjectType} from './config' 8 | import {INFERNO_APP, PREACT_APP, REACT_APP, WEB_APP} from './constants' 9 | import createServerWebpackConfig from './createServerWebpackConfig' 10 | import debug from './debug' 11 | import {deepToString, joinAnd} from './utils' 12 | 13 | const APP_TYPE_CONFIG = { 14 | [INFERNO_APP]: './inferno', 15 | [PREACT_APP]: './preact', 16 | [REACT_APP]: './react', 17 | [WEB_APP]: './web', 18 | } 19 | 20 | /** 21 | * Express middleware for serving an app with hot reloading - equivalent to 22 | * having run `nwb serve`, but from your own server. 23 | */ 24 | export default function nwbMiddleware(express: Object, options: Object = {}) { 25 | assert( 26 | express && typeof express.Router === 'function', 27 | 'The express module must be passed as the first argument to nwb middleware' 28 | ) 29 | 30 | let projectType = options.type 31 | if (projectType == null) { 32 | projectType = getProjectType({_: ['serve'], config: options.config}) 33 | } 34 | if (!APP_TYPE_CONFIG[projectType]) { 35 | throw new Error( 36 | `nwb Express middleware is unable to handle '${projectType}' projects, only ` + 37 | joinAnd(Object.keys(APP_TYPE_CONFIG).map(s => `'${s}'`), 'or') 38 | ) 39 | } 40 | 41 | // Use options to create an object equivalent to CLI args parsed by minimist 42 | let args = { 43 | _: [`serve-${projectType}`, options.entry], 44 | config: options.config, 45 | hmr: options.hmr !== false, 46 | install: !!options.install || !!options.autoInstall, 47 | reload: !!options.reload 48 | } 49 | 50 | let appTypeConfig = require(APP_TYPE_CONFIG[projectType])(args) 51 | 52 | let webpackConfig = createServerWebpackConfig( 53 | args, 54 | createServeConfig(args, appTypeConfig.getServeConfig(), { 55 | stats: 'none', 56 | plugins: { 57 | status: { 58 | disableClearConsole: true, 59 | successMessage: null, 60 | } 61 | } 62 | }) 63 | ) 64 | 65 | debug('webpack config: %s', deepToString(webpackConfig)) 66 | 67 | let compiler = webpack(webpackConfig) 68 | 69 | let router = express.Router() 70 | 71 | router.use(require('webpack-dev-middleware')(compiler, { 72 | publicPath: webpackConfig.output.publicPath, 73 | })) 74 | 75 | router.use(require('webpack-hot-middleware')(compiler, { 76 | log: false 77 | })) 78 | 79 | return router 80 | } 81 | -------------------------------------------------------------------------------- /src/inferno/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import resolve from 'resolve' 3 | 4 | import {modulePath} from '../utils' 5 | 6 | function getBaseConfig() { 7 | let config: Object = { 8 | babel: { 9 | presets: [require.resolve('./inferno-preset')] 10 | }, 11 | // Allow compatible React components to be used 12 | resolve: { 13 | alias: { 14 | 'react': 'inferno-compat', 15 | 'react-dom': 'inferno-compat', 16 | } 17 | }, 18 | } 19 | // Inferno's default module build is the production version - use the 20 | // development version for development and testing. 21 | if (process.env.NODE_ENV !== 'production') { 22 | config.resolve.alias['inferno'] = resolve.sync('inferno/dist/index.dev.esm.js', { 23 | basedir: process.cwd() 24 | }) 25 | } 26 | return config 27 | } 28 | 29 | function getDependencies() { 30 | return ['inferno'] 31 | } 32 | 33 | function getCompatDependencies() { 34 | return ['inferno-compat', 'inferno-clone-vnode', 'inferno-create-class', 'inferno-create-element'] 35 | } 36 | 37 | function getQuickConfig() { 38 | return { 39 | commandConfig: getBaseConfig(), 40 | defaultTitle: 'Inferno App', 41 | renderShim: require.resolve('./renderShim'), 42 | renderShimAliases: { 43 | 'inferno': modulePath('inferno'), 44 | } 45 | } 46 | } 47 | 48 | export default (args: Object) => ({ 49 | getName: () => 'Inferno', 50 | getProjectDefaults() { 51 | return {compat: false} 52 | }, 53 | getProjectDependencies(answers: Object): string[] { 54 | let deps = getDependencies() 55 | if (answers.compat) { 56 | deps = deps.concat(getCompatDependencies()) 57 | } 58 | return deps 59 | }, 60 | getProjectQuestions() { 61 | let defaults = this.getProjectDefaults() 62 | return [{ 63 | when: () => !('compat' in args), 64 | type: 'confirm', 65 | name: 'compat', 66 | message: 'Do you want to use inferno-compat so you can use React modules?', 67 | default: defaults.compat, 68 | }] 69 | }, 70 | getBuildDependencies: () => [], 71 | getBuildConfig: getBaseConfig, 72 | getServeConfig: getBaseConfig, 73 | getQuickDependencies: (): string[] => getDependencies().concat(getCompatDependencies()), 74 | getQuickBuildConfig: getQuickConfig, 75 | getQuickServeConfig: getQuickConfig, 76 | getKarmaTestConfig: getBaseConfig, 77 | }) 78 | -------------------------------------------------------------------------------- /src/inferno/inferno-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return { 3 | plugins: [ 4 | require.resolve('@babel/plugin-syntax-jsx'), 5 | require.resolve('babel-plugin-inferno'), 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/inferno/renderShim.js: -------------------------------------------------------------------------------- 1 | /* global NWB_QUICK_MOUNT_ID */ 2 | 3 | let Inferno = require('inferno') 4 | 5 | let parent = document.getElementById(NWB_QUICK_MOUNT_ID) 6 | let vnode = null 7 | 8 | function renderEntry(exported) { 9 | if (exported.default) { 10 | exported = exported.default 11 | } 12 | // Assumptions: the entry module either renders the app itself or exports an 13 | // Inferno component (which is either a function or class) or VNode (which has 14 | // a flags property). 15 | if (Object.prototype.toString.call(exported) === '[object Function]') { 16 | vnode = Inferno.createComponentVNode(1 << 1 /* === VNodeFlags.ComponentUnknown */, exported) 17 | } 18 | else if (exported.flags) { 19 | vnode = exported 20 | } 21 | else { 22 | // Assumption: the entry module rendered the app 23 | return 24 | } 25 | Inferno.render(vnode, parent) 26 | } 27 | 28 | function init() { 29 | renderEntry(require('nwb-quick-entry')) 30 | } 31 | 32 | if (module.hot) { 33 | module.hot.accept('nwb-quick-entry', init) 34 | } 35 | 36 | init() 37 | -------------------------------------------------------------------------------- /src/karmaServer.js: -------------------------------------------------------------------------------- 1 | import {Server, config} from 'karma' 2 | 3 | import {getPluginConfig, getUserConfig} from './config' 4 | import createKarmaConfig from './createKarmaConfig' 5 | import {KarmaExitCodeError} from './errors' 6 | 7 | export default function karmaServer(args, buildConfig, cb) { 8 | // Force the environment to test 9 | process.env.NODE_ENV = 'test' 10 | 11 | let pluginConfig = getPluginConfig(args) 12 | let userConfig = getUserConfig(args, {pluginConfig}) 13 | let karmaConfig = createKarmaConfig(args, buildConfig, pluginConfig, userConfig) 14 | 15 | new Server(config.parseConfig(null, karmaConfig), (exitCode) => { 16 | if (exitCode !== 0) return cb(new KarmaExitCodeError(exitCode)) 17 | cb() 18 | }).start() 19 | } 20 | -------------------------------------------------------------------------------- /src/preact/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {modulePath} from '../utils' 3 | 4 | function getBaseConfig() { 5 | return { 6 | babel: { 7 | presets: [require.resolve('./preact-preset')] 8 | }, 9 | // Allow compatible React components to be used 10 | resolve: { 11 | alias: { 12 | 'react': 'preact/compat', 13 | 'react-dom/test-utils': 'preact/test-utils', 14 | 'react-dom': 'preact/compat', 15 | } 16 | }, 17 | } 18 | } 19 | 20 | function getDependencies() { 21 | return ['preact'] 22 | } 23 | 24 | function getQuickConfig() { 25 | return { 26 | commandConfig: getBaseConfig(), 27 | defaultTitle: 'Preact App', 28 | renderShim: require.resolve('./renderShim'), 29 | renderShimAliases: { 30 | 'preact': modulePath('preact'), 31 | } 32 | } 33 | } 34 | 35 | export default (args: Object) => ({ 36 | getName: () => 'Preact', 37 | getProjectDefaults() { 38 | return {} 39 | }, 40 | getProjectDependencies(): string[] { 41 | return getDependencies() 42 | }, 43 | getProjectQuestions() { 44 | return null 45 | }, 46 | getBuildDependencies: () => [], 47 | getBuildConfig: getBaseConfig, 48 | getServeConfig: getBaseConfig, 49 | getQuickDependencies: (): string[] => getDependencies(), 50 | getQuickBuildConfig: getQuickConfig, 51 | getQuickServeConfig: getQuickConfig, 52 | getKarmaTestConfig: getBaseConfig, 53 | }) 54 | -------------------------------------------------------------------------------- /src/preact/preact-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return { 3 | plugins: [ 4 | [require.resolve('@babel/plugin-transform-react-jsx'), { 5 | pragma: 'h', 6 | pragmaFrag: 'Fragment', 7 | }] 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/preact/renderShim.js: -------------------------------------------------------------------------------- 1 | /* global NWB_QUICK_MOUNT_ID */ 2 | 3 | // Enable use of Preact Devtools 4 | if (process.env.NODE_ENV === 'development') { 5 | require('preact/debug') 6 | } 7 | 8 | let Preact = require('preact') 9 | 10 | let parent = document.getElementById(NWB_QUICK_MOUNT_ID) 11 | let root = parent.firstElementChild 12 | let vnode = null 13 | 14 | function renderEntry(exported) { 15 | if (exported.default) { 16 | exported = exported.default 17 | } 18 | // Assumptions: the entry module either renders the app itself or exports a 19 | // Preact component (which is either a function or class) or VNode (which has 20 | // a children property). 21 | if (Object.prototype.toString.call(exported) === '[object Function]') { 22 | vnode = Preact.h(exported) 23 | } 24 | else if (exported.children) { 25 | vnode = exported 26 | } 27 | else { 28 | // Assumption: the entry module rendered the app 29 | return 30 | } 31 | root = Preact.render(vnode, parent, root) 32 | } 33 | 34 | function init() { 35 | renderEntry(require('nwb-quick-entry')) 36 | } 37 | 38 | if (module.hot) { 39 | module.hot.accept('nwb-quick-entry', init) 40 | } 41 | 42 | init() 43 | -------------------------------------------------------------------------------- /src/react/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import path from 'path' 3 | 4 | import {modulePath} from '../utils' 5 | 6 | function getBaseConfig(): Object { 7 | return { 8 | babel: { 9 | presets: [ 10 | // User-configurable, so handled by createBabelConfig 11 | 'react' 12 | ] 13 | }, 14 | } 15 | } 16 | 17 | function getBaseDependencies() { 18 | return ['react', 'react-dom'] 19 | } 20 | 21 | function getBuildConfig(args, options: {useModulePath?: boolean} = {}) { 22 | let config = getBaseConfig() 23 | 24 | if (process.env.NODE_ENV === 'production') { 25 | // User-configurable, so handled by createBabelConfig 26 | config.babel.presets.push('react-prod') 27 | } 28 | 29 | let aliasPath = options.useModulePath ? modulePath : (alias) => alias 30 | 31 | if (args.inferno || args['inferno-compat']) { 32 | config.resolve = { 33 | alias: { 34 | 'react': aliasPath('inferno-compat'), 35 | 'react-dom': aliasPath('inferno-compat'), 36 | }, 37 | } 38 | } 39 | else if (args.preact || args['preact-compat']) { 40 | let preactCompathPath = path.join(aliasPath('preact'), 'compat') 41 | config.resolve = { 42 | alias: { 43 | 'react': preactCompathPath, 44 | 'react-dom/test-utils': path.join(aliasPath('preact'), 'test-utils'), 45 | 'react-dom': preactCompathPath, 46 | }, 47 | } 48 | } 49 | 50 | return config 51 | } 52 | 53 | class ReactConfig { 54 | _args: Object; 55 | 56 | constructor(args: Object) { 57 | this._args = args 58 | } 59 | 60 | _getCompatDependencies() { 61 | if (this._args.inferno || this._args['inferno-compat']) { 62 | return ['inferno', 'inferno-compat', 'inferno-clone-vnode', 'inferno-create-class', 'inferno-create-element'] 63 | } 64 | else if (this._args.preact || this._args['preact-compat']) { 65 | return ['preact'] 66 | } 67 | return [] 68 | } 69 | 70 | _getCompatName() { 71 | if (this._args.inferno || this._args['inferno-compat']) { 72 | return 'Inferno (React compat)' 73 | } 74 | else if (this._args.preact || this._args['preact-compat']) { 75 | return 'Preact (React compat)' 76 | } 77 | return 'React' 78 | } 79 | 80 | _getQuickConfig() { 81 | return { 82 | defaultTitle: `${this.getName()} App`, 83 | renderShim: require.resolve('./renderShim'), 84 | renderShimAliases: { 85 | 'react': modulePath('react'), 86 | 'react-dom': modulePath('react-dom'), 87 | }, 88 | } 89 | } 90 | 91 | getName = () => { 92 | if (/^build/.test(this._args._[0])) { 93 | return this._getCompatName() 94 | } 95 | return 'React' 96 | } 97 | 98 | getProjectDefaults() { 99 | return {} 100 | } 101 | 102 | getProjectDependencies() { 103 | return getBaseDependencies() 104 | } 105 | 106 | getProjectQuestions() { 107 | return null 108 | } 109 | 110 | getBuildDependencies = () => { 111 | return this._getCompatDependencies() 112 | } 113 | 114 | getBuildConfig = () => { 115 | return getBuildConfig(this._args) 116 | } 117 | 118 | getServeConfig = () => { 119 | let config = getBaseConfig() 120 | 121 | if (this._args.hmr !== false) { 122 | config.babel.plugins = [ 123 | [require.resolve('react-refresh/babel'), { 124 | skipEnvCheck: Boolean(process.env.NWB_TEST) 125 | }] 126 | ] 127 | config.plugins = {reactRefresh: true} 128 | } 129 | 130 | return config 131 | } 132 | 133 | getQuickDependencies = (): string[] => { 134 | let deps = getBaseDependencies() 135 | if (/^build/.test(this._args._[0])) { 136 | deps = deps.concat(this._getCompatDependencies()) 137 | } 138 | return deps 139 | } 140 | 141 | getQuickBuildConfig = () => { 142 | return { 143 | commandConfig: getBuildConfig(this._args, {useModulePath: true}), 144 | ...this._getQuickConfig(), 145 | } 146 | } 147 | 148 | getQuickServeConfig = () => { 149 | return { 150 | commandConfig: this.getServeConfig(), 151 | ...this._getQuickConfig(), 152 | } 153 | } 154 | 155 | getKarmaTestConfig() { 156 | return getBaseConfig() 157 | } 158 | } 159 | 160 | export default (args: Object) => new ReactConfig(args) 161 | -------------------------------------------------------------------------------- /src/react/renderShim.js: -------------------------------------------------------------------------------- 1 | /* global NWB_QUICK_MOUNT_ID */ 2 | 3 | let React = require('react') 4 | let ReactDOM = require('react-dom') 5 | 6 | let parent = document.getElementById(NWB_QUICK_MOUNT_ID) 7 | let element = null 8 | 9 | function renderEntry(exported) { 10 | if (exported.default) { 11 | exported = exported.default 12 | } 13 | // Assumptions: the entry module either renders the app itself or exports a 14 | // React component (which is either a function or class) or element (which has 15 | // type and props properties). 16 | if (Object.prototype.toString.call(exported) === '[object Function]') { 17 | element = React.createElement(exported) 18 | } 19 | else if (exported.type && exported.props) { 20 | element = exported 21 | } 22 | else { 23 | // Assumption: the entry module rendered the app 24 | return 25 | } 26 | ReactDOM.render(element, parent) 27 | } 28 | 29 | function init() { 30 | renderEntry(require('nwb-quick-entry')) 31 | } 32 | 33 | if (module.hot) { 34 | module.hot.accept('nwb-quick-entry', init) 35 | } 36 | 37 | init() 38 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type ErrBack = (err?: ?Error) => void; 3 | 4 | export type ServerConfig = { 5 | port?: number, 6 | historyApiFallback?: boolean | Object, 7 | host?: string, 8 | }; 9 | -------------------------------------------------------------------------------- /src/web/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | function getBaseConfig() { 4 | return {} 5 | } 6 | 7 | function getDependencies() { 8 | return [] 9 | } 10 | 11 | function getQuickConfig() { 12 | return { 13 | commandConfig: getBaseConfig(), 14 | defaultTitle: 'Web App', 15 | } 16 | } 17 | 18 | // Vanilla JavaScript apps just use the default config for everything 19 | export default (args: Object) => ({ 20 | getName: () => 'Web', 21 | getProjectDefaults: () => ({}), 22 | getProjectDependencies: getDependencies, 23 | getProjectQuestions: () => null, 24 | getBuildDependencies: getDependencies, 25 | getBuildConfig: getBaseConfig, 26 | getServeConfig: getBaseConfig, 27 | getQuickDependencies: getDependencies, 28 | getQuickBuildConfig: getQuickConfig, 29 | getQuickServeConfig() { 30 | // Reload on unaccepted HMR changes by default; disable with --no-reload 31 | if (args.reload !== false) { 32 | args.reload = true 33 | } 34 | return getQuickConfig() 35 | }, 36 | getKarmaTestConfig: getBaseConfig, 37 | }) 38 | -------------------------------------------------------------------------------- /src/webpackBuild.js: -------------------------------------------------------------------------------- 1 | import ora from 'ora' 2 | import webpack from 'webpack' 3 | 4 | import {getPluginConfig, getUserConfig} from './config' 5 | import createWebpackConfig from './createWebpackConfig' 6 | import debug from './debug' 7 | import {deepToString} from './utils' 8 | import {logBuildResults} from './webpackUtils' 9 | 10 | /** 11 | * If you pass a non-falsy type, this will handle spinner display and output 12 | * logging itself, otherwise use the stats provided in the callback. 13 | */ 14 | export default function webpackBuild(type, args, buildConfig, cb) { 15 | if (!process.env.NODE_ENV) { 16 | process.env.NODE_ENV = 'production' 17 | } 18 | 19 | let pluginConfig = getPluginConfig(args) 20 | let userConfig 21 | try { 22 | userConfig = getUserConfig(args, {pluginConfig}) 23 | } 24 | catch (e) { 25 | return cb(e) 26 | } 27 | 28 | if (typeof buildConfig == 'function') { 29 | buildConfig = buildConfig(args) 30 | } 31 | 32 | let webpackConfig 33 | try { 34 | webpackConfig = createWebpackConfig(buildConfig, pluginConfig, userConfig) 35 | } 36 | catch (e) { 37 | return cb(e) 38 | } 39 | 40 | debug('webpack config: %s', deepToString(webpackConfig)) 41 | 42 | let spinner 43 | if (type) { 44 | spinner = ora(`Building ${type}`).start() 45 | } 46 | let compiler = webpack(webpackConfig) 47 | compiler.run((err, stats) => { 48 | if (err) { 49 | if (spinner) { 50 | spinner.fail() 51 | } 52 | return cb(err) 53 | } 54 | if (spinner || stats.hasErrors()) { 55 | logBuildResults(stats, spinner) 56 | } 57 | if (stats.hasErrors()) { 58 | return cb(new Error('Build failed with errors.')) 59 | } 60 | cb(null, stats) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/webpackServer.js: -------------------------------------------------------------------------------- 1 | import {yellow} from 'chalk' 2 | import detect from 'detect-port' 3 | import inquirer from 'inquirer' 4 | 5 | import {getPluginConfig, getUserConfig} from './config' 6 | import {DEFAULT_PORT} from './constants' 7 | import createServerWebpackConfig from './createServerWebpackConfig' 8 | import debug from './debug' 9 | import devServer from './devServer' 10 | import {clearConsole, deepToString, typeOf} from './utils' 11 | 12 | /** 13 | * Get the port to run the server on, detecting if the intended port is 14 | * available first and prompting the user if not. 15 | */ 16 | function getServerPort(args, intendedPort, cb) { 17 | detect(intendedPort, (err, suggestedPort) => { 18 | if (err) return cb(err) 19 | // No need to prompt if the intended port is available 20 | if (suggestedPort === intendedPort) return cb(null, suggestedPort) 21 | // Support use of --force to avoid interactive prompt 22 | if (args.force) return cb(null, suggestedPort) 23 | 24 | if (args.clear !== false && args.clearConsole !== false) { 25 | clearConsole() 26 | } 27 | console.log(yellow(`Something is already running on port ${intendedPort}.`)) 28 | console.log() 29 | inquirer.prompt([ 30 | { 31 | type: 'confirm', 32 | name: 'run', 33 | message: 'Would you like to run the app on another port instead?', 34 | default: true, 35 | }, 36 | ]).then( 37 | ({run}) => cb(null, run ? suggestedPort : null), 38 | (err) => cb(err) 39 | ) 40 | }) 41 | } 42 | 43 | /** 44 | * Start a development server with Webpack using a given build configuration. 45 | */ 46 | export default function webpackServer(args, buildConfig, cb) { 47 | // Default environment to development - we also run the dev server while 48 | // testing to check that HMR works. 49 | if (!process.env.NODE_ENV) { 50 | process.env.NODE_ENV = 'development' 51 | } 52 | 53 | if (typeof buildConfig == 'function') { 54 | buildConfig = buildConfig(args) 55 | } 56 | 57 | let serverConfig 58 | try { 59 | let pluginConfig = getPluginConfig(args) 60 | serverConfig = getUserConfig(args, {pluginConfig}).devServer 61 | } 62 | catch (e) { 63 | return cb(e) 64 | } 65 | 66 | getServerPort(args, args.port || Number(serverConfig.port) || DEFAULT_PORT, (err, port) => { 67 | if (err) return cb(err) 68 | // A null port indicates the user chose not to run the server when prompted 69 | if (port === null) return cb() 70 | 71 | serverConfig.port = port 72 | // Fallback index serving can be disabled with --no-fallback 73 | if (args.fallback === false) { 74 | serverConfig.historyApiFallback = false 75 | } 76 | // Fallback index serving can be configured with dot arguments 77 | // e.g. --fallback.disableDotRule --fallback.verbose 78 | else if (typeOf(args.fallback) === 'object') { 79 | serverConfig.historyApiFallback = args.fallback 80 | } 81 | // The host can be overridden with --host 82 | if (args.host) serverConfig.host = args.host 83 | // Open a browser with --open (default browser) or --open="browser name" 84 | if (args.open) serverConfig.open = args.open 85 | 86 | let url = `http${serverConfig.https ? 's' : ''}://${serverConfig.host || 'localhost'}:${port}/` 87 | 88 | if (!('status' in buildConfig.plugins)) { 89 | buildConfig.plugins.status = { 90 | disableClearConsole: args.clear === false || args['clear-console'] === false, 91 | successMessage: 92 | `The app is running at ${url}`, 93 | } 94 | } 95 | 96 | let webpackConfig 97 | try { 98 | webpackConfig = createServerWebpackConfig(args, buildConfig, serverConfig) 99 | } 100 | catch (e) { 101 | return cb(e) 102 | } 103 | 104 | debug('webpack config: %s', deepToString(webpackConfig)) 105 | 106 | devServer(webpackConfig, serverConfig, url, cb) 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /templates/inferno-app/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the app's root directory will install everything you need for development. 8 | 9 | ## Development Server 10 | 11 | - `npm start` will run the app's development server at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` creates a production build by default. 24 | 25 | To create a development build, set the `NODE_ENV` environment variable to `development` while running this command. 26 | 27 | - `npm run clean` will delete built resources. 28 | -------------------------------------------------------------------------------- /templates/inferno-app/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | Describe {{name}} here. 4 | -------------------------------------------------------------------------------- /templates/inferno-app/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /templates/inferno-app/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/inferno-app/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "Describe {{name}} here", 5 | "private": true, 6 | "scripts": { 7 | "build": "nwb build-inferno-app", 8 | "clean": "nwb clean-app", 9 | "start": "nwb serve-inferno-app", 10 | "test": "nwb test-inferno", 11 | "test:coverage": "nwb test-inferno --coverage", 12 | "test:watch": "nwb test-inferno --server" 13 | }, 14 | "dependencies": { 15 | }, 16 | "devDependencies": { 17 | "nwb": "{{nwbVersion}}" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "repository": "" 22 | } 23 | -------------------------------------------------------------------------------- /templates/inferno-app/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'inferno-app' 3 | } 4 | -------------------------------------------------------------------------------- /templates/inferno-app/public/_.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/templates/inferno-app/public/_.gitkeep -------------------------------------------------------------------------------- /templates/inferno-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | height: 100%; 3 | min-height: 400px; 4 | text-align: center; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: stretch; 8 | } 9 | 10 | .App-flex { 11 | flex: 1; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | .App-heading { 18 | background-color: #fff; 19 | color: #444; 20 | font-size: 6vh; 21 | z-index: 2; 22 | } 23 | 24 | .App-logo { 25 | max-height: 30vh; 26 | max-width: 30vh; 27 | margin-right: 1em; 28 | } 29 | 30 | .App-instructions { 31 | background-color: #fafafa; 32 | color: #545454; 33 | font-size: 3vh; 34 | line-height: 1.5; 35 | padding: 0 1em; 36 | } 37 | 38 | .App-instructions code { 39 | background-color: #f40026; 40 | color: #fff; 41 | padding: .2em .3em; 42 | border-radius: 1em; 43 | } 44 | -------------------------------------------------------------------------------- /templates/inferno-app/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | 3 | import {Component} from 'inferno' 4 | 5 | class App extends Component { 6 | render() { 7 | return <div className="App"> 8 | <div className="App-heading App-flex"> 9 | <h2>Welcome to <span className="App-inferno">Inferno</span></h2> 10 | </div> 11 | <div className="App-instructions App-flex"> 12 | <img className="App-logo" src={require('./inferno.svg')}/> 13 | <p>Edit <code>src/App.js</code> and save to hot reload your changes.</p> 14 | </div> 15 | </div> 16 | } 17 | } 18 | 19 | export default App 20 | -------------------------------------------------------------------------------- /templates/inferno-app/src/index.css: -------------------------------------------------------------------------------- 1 | html, body, #app { 2 | height: 100%; 3 | } 4 | body { 5 | font-family: sans-serif; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | -------------------------------------------------------------------------------- /templates/inferno-app/src/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 6 | <meta http-equiv="x-ua-compatible" content="ie=edge"> 7 | <title>{{name}} 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/inferno-app/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import {render} from 'inferno' 4 | 5 | import App from './App' 6 | 7 | render(, document.querySelector('#app')) 8 | 9 | if (module.hot) { 10 | module.hot.accept() 11 | } 12 | -------------------------------------------------------------------------------- /templates/inferno-app/src/inferno.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /templates/inferno-app/tests/App.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import {render} from 'inferno' 3 | 4 | import App from 'src/App' 5 | 6 | describe('App component', () => { 7 | let node 8 | 9 | beforeEach(() => { 10 | node = document.createElement('div') 11 | }) 12 | 13 | afterEach(() => { 14 | render(null, node) 15 | }) 16 | 17 | it('displays a welcome message', () => { 18 | render(, node) 19 | expect(node.textContent).toContain('Welcome to Inferno') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /templates/inferno-app/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/preact-app/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the app's root directory will install everything you need for development. 8 | 9 | ## Development Server 10 | 11 | - `npm start` will run the app's development server at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` creates a production build by default. 24 | 25 | To create a development build, set the `NODE_ENV` environment variable to `development` while running this command. 26 | 27 | - `npm run clean` will delete built resources. 28 | -------------------------------------------------------------------------------- /templates/preact-app/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | Describe {{name}} here. 4 | -------------------------------------------------------------------------------- /templates/preact-app/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /templates/preact-app/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/preact-app/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "Describe {{name}} here", 5 | "private": true, 6 | "scripts": { 7 | "build": "nwb build-preact-app", 8 | "clean": "nwb clean-app", 9 | "start": "nwb serve-preact-app", 10 | "test": "nwb test-preact", 11 | "test:coverage": "nwb test-preact --coverage", 12 | "test:watch": "nwb test-preact --server" 13 | }, 14 | "dependencies": { 15 | }, 16 | "devDependencies": { 17 | "nwb": "{{nwbVersion}}" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "repository": "" 22 | } 23 | -------------------------------------------------------------------------------- /templates/preact-app/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'preact-app' 3 | } 4 | -------------------------------------------------------------------------------- /templates/preact-app/public/_.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/templates/preact-app/public/_.gitkeep -------------------------------------------------------------------------------- /templates/preact-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | height: 100%; 3 | min-height: 400px; 4 | text-align: center; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: stretch; 8 | } 9 | 10 | .App-flex { 11 | flex: 1; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | .App-heading { 18 | background-color: #343a47; 19 | color: #f2777a; 20 | font-size: 6vh; 21 | z-index: 2; 22 | } 23 | 24 | .App-logo { 25 | max-height: 30vh; 26 | max-width: 30vh; 27 | } 28 | 29 | .App-instructions { 30 | background-color: #fff; 31 | color: #000; 32 | font-size: 3vh; 33 | line-height: 1.5; 34 | padding: 0 1em; 35 | } 36 | 37 | .App-instructions code { 38 | background-color: #343a47; 39 | color: #f2777a; 40 | padding: .2em .3em; 41 | border-radius: .2em; 42 | } 43 | -------------------------------------------------------------------------------- /templates/preact-app/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | 3 | import {h, Component} from 'preact' 4 | 5 | export default class App extends Component { 6 | render() { 7 | return
8 |
9 |

10 | Welcome to Preact 11 |

12 |
13 |
14 | 15 |

Edit src/App.js and save to hot reload your changes.

16 |
17 |
18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/preact-app/src/index.css: -------------------------------------------------------------------------------- 1 | html, body, #app { 2 | height: 100%; 3 | } 4 | body { 5 | font-family: sans-serif; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | -------------------------------------------------------------------------------- /templates/preact-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{name}} 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/preact-app/src/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'development') { 2 | // Enable use of Preact Devtools 3 | require('preact/debug') 4 | } 5 | 6 | import './index.css' 7 | 8 | import {h, render} from 'preact' 9 | 10 | function init() { 11 | let App = require('./App').default 12 | render(, document.querySelector('#app')) 13 | } 14 | 15 | if (module.hot) { 16 | module.hot.accept('./App', init) 17 | } 18 | 19 | init() 20 | -------------------------------------------------------------------------------- /templates/preact-app/src/preact-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /templates/preact-app/src/preact-name.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /templates/preact-app/tests/App.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import {h, render} from 'preact' 3 | 4 | import App from 'src/App' 5 | 6 | describe('App component', () => { 7 | let node 8 | 9 | beforeEach(() => { 10 | node = document.createElement('div') 11 | }) 12 | 13 | afterEach(() => { 14 | render(null, node) 15 | }) 16 | 17 | it('displays a welcome message', () => { 18 | render(, node) 19 | expect(node.textContent).toContain('Welcome to') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /templates/preact-app/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/react-app/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the app's root directory will install everything you need for development. 8 | 9 | ## Development Server 10 | 11 | - `npm start` will run the app's development server at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` creates a production build by default. 24 | 25 | To create a development build, set the `NODE_ENV` environment variable to `development` while running this command. 26 | 27 | - `npm run clean` will delete built resources. 28 | -------------------------------------------------------------------------------- /templates/react-app/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | Describe {{name}} here. 4 | -------------------------------------------------------------------------------- /templates/react-app/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /templates/react-app/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/react-app/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "Describe {{name}} here", 5 | "private": true, 6 | "scripts": { 7 | "build": "nwb build-react-app", 8 | "clean": "nwb clean-app", 9 | "start": "nwb serve-react-app", 10 | "test": "nwb test-react", 11 | "test:coverage": "nwb test-react --coverage", 12 | "test:watch": "nwb test-react --server" 13 | }, 14 | "dependencies": { 15 | }, 16 | "devDependencies": { 17 | "nwb": "{{nwbVersion}}" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "repository": "" 22 | } 23 | -------------------------------------------------------------------------------- /templates/react-app/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-app' 3 | } 4 | -------------------------------------------------------------------------------- /templates/react-app/public/_.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/templates/react-app/public/_.gitkeep -------------------------------------------------------------------------------- /templates/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | height: 100%; 3 | min-height: 400px; 4 | text-align: center; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: stretch; 8 | } 9 | 10 | .App-flex { 11 | flex: 1; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | .App-heading { 18 | background-color: #222; 19 | color: #f8f8f8; 20 | font-size: 6vh; 21 | box-shadow: 0px 4px 4vh 4px rgba(34,34,34,0.9); 22 | z-index: 2; 23 | } 24 | 25 | .App-react { 26 | color: #00d8ff; 27 | text-decoration: overline underline; 28 | } 29 | 30 | .App-logo { 31 | max-height: 30vh; 32 | max-width: 30vh; 33 | } 34 | 35 | .App-instructions { 36 | background-color: #f8f8f8; 37 | color: #222; 38 | font-size: 3vh; 39 | line-height: 1.5; 40 | padding: 0 1em; 41 | } 42 | 43 | .App-instructions code { 44 | background-color: #222; 45 | color: #00d8ff; 46 | padding: .2em .3em; 47 | border-radius: .2em; 48 | } 49 | -------------------------------------------------------------------------------- /templates/react-app/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | 3 | import React, {Component} from 'react' 4 | 5 | class App extends Component { 6 | render() { 7 | return
8 |
9 |

Welcome to React

10 |
11 |
12 | 13 |

Edit src/App.js and save to hot reload your changes.

14 |
15 |
16 | } 17 | } 18 | 19 | export default App 20 | -------------------------------------------------------------------------------- /templates/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | html, body, #app { 2 | height: 100%; 3 | } 4 | body { 5 | font-family: sans-serif; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | -------------------------------------------------------------------------------- /templates/react-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{name}} 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/react-app/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import {render} from 'react-dom' 5 | 6 | import App from './App' 7 | 8 | render(, document.querySelector('#app')) 9 | -------------------------------------------------------------------------------- /templates/react-app/src/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/react-app/tests/App.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import App from 'src/App' 6 | 7 | describe('App component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.textContent).toContain('Welcome to React') 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /templates/react-app/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/react-component/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the component's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /templates/react-component/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | [![Travis][build-badge]][build] 4 | [![npm package][npm-badge]][npm] 5 | [![Coveralls][coveralls-badge]][coveralls] 6 | 7 | Describe {{name}} here. 8 | 9 | [build-badge]: https://img.shields.io/travis/user/repo/master.png?style=flat-square 10 | [build]: https://travis-ci.org/user/repo 11 | 12 | [npm-badge]: https://img.shields.io/npm/v/npm-package.png?style=flat-square 13 | [npm]: https://www.npmjs.org/package/npm-package 14 | 15 | [coveralls-badge]: https://img.shields.io/coveralls/user/repo/master.png?style=flat-square 16 | [coveralls]: https://coveralls.io/github/user/repo 17 | -------------------------------------------------------------------------------- /templates/react-component/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | -------------------------------------------------------------------------------- /templates/react-component/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/react-component/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "{{name}} React component", 5 | "main": "lib/index.js",{{esModulesPackageConfig}} 6 | "files": [ 7 | "css", 8 | "es", 9 | "lib", 10 | "umd" 11 | ], 12 | "scripts": { 13 | "build": "nwb build-react-component", 14 | "clean": "nwb clean-module && nwb clean-demo", 15 | "prepublishOnly": "npm run build", 16 | "start": "nwb serve-react-demo", 17 | "test": "nwb test-react", 18 | "test:coverage": "nwb test-react --coverage", 19 | "test:watch": "nwb test-react --server" 20 | }, 21 | "dependencies": { 22 | }, 23 | "peerDependencies": { 24 | "react": "{{reactPeerVersion}}" 25 | }, 26 | "devDependencies": { 27 | "nwb": "{{nwbVersion}}" 28 | }, 29 | "author": "", 30 | "homepage": "", 31 | "license": "MIT", 32 | "repository": "", 33 | "keywords": [ 34 | "react-component" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /templates/react-component/demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {render} from 'react-dom' 3 | 4 | import Example from '../../src' 5 | 6 | export default class Demo extends Component { 7 | render() { 8 | return
9 |

{{name}} Demo

10 | 11 |
12 | } 13 | } 14 | 15 | render(, document.querySelector('#demo')) 16 | -------------------------------------------------------------------------------- /templates/react-component/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component' 3 | } 4 | -------------------------------------------------------------------------------- /templates/react-component/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | export default class extends Component { 4 | render() { 5 | return
6 |

Welcome to React components

7 |
8 | } 9 | } 10 | -------------------------------------------------------------------------------- /templates/react-component/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/react-component/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /templates/web-app/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the app's root directory will install everything you need for development. 8 | 9 | ## Development Server 10 | 11 | - `npm start` will run the app's development server at [http://localhost:3000](http://localhost:3000), automatically reloading the page on every change. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` creates a production build by default. 24 | 25 | To create a development build, set the `NODE_ENV` environment variable to `development` while running this command. 26 | 27 | - `npm run clean` will delete built resources. 28 | -------------------------------------------------------------------------------- /templates/web-app/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | Describe {{name}} here. 4 | -------------------------------------------------------------------------------- /templates/web-app/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /templates/web-app/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/web-app/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "Describe {{name}} here", 5 | "private": true, 6 | "scripts": { 7 | "build": "nwb build-web-app", 8 | "clean": "nwb clean-app", 9 | "start": "nwb serve-web-app --reload", 10 | "test": "nwb test", 11 | "test:coverage": "nwb test --coverage", 12 | "test:watch": "nwb test --server" 13 | }, 14 | "dependencies": { 15 | }, 16 | "devDependencies": { 17 | "nwb": "{{nwbVersion}}" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "repository": "" 22 | } 23 | -------------------------------------------------------------------------------- /templates/web-app/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'web-app' 3 | } 4 | -------------------------------------------------------------------------------- /templates/web-app/public/_.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/nwb/d95ab83f05d09c40dd2b7c65f4c6b15776c8526d/templates/web-app/public/_.gitkeep -------------------------------------------------------------------------------- /templates/web-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{name}} 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/web-app/src/index.js: -------------------------------------------------------------------------------- 1 | let app = document.querySelector('#app') 2 | 3 | app.innerHTML = '

Welcome to {{name}}

' 4 | -------------------------------------------------------------------------------- /templates/web-app/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/web-app/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | 3 | describe('App template', () => { 4 | let app 5 | 6 | before(() => { 7 | app = document.createElement('div') 8 | app.id = 'app' 9 | document.body.appendChild(app) 10 | }) 11 | 12 | after(() => { 13 | document.body.removeChild(app) 14 | }) 15 | 16 | it('displays a welcome message', () => { 17 | require('src/index') 18 | expect(app.innerHTML).toContain('Welcome to {{name}}') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /templates/web-module/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 10 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the module's root directory will install everything you need for development. 8 | 9 | ## Running Tests 10 | 11 | - `npm test` will run the tests once. 12 | 13 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 14 | 15 | - `npm run test:watch` will run the tests on every change. 16 | 17 | ## Building 18 | 19 | - `npm run build` will build the module for publishing to npm. 20 | 21 | - `npm run clean` will delete built resources. 22 | -------------------------------------------------------------------------------- /templates/web-module/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | [![Travis][build-badge]][build] 4 | [![npm package][npm-badge]][npm] 5 | [![Coveralls][coveralls-badge]][coveralls] 6 | 7 | Describe {{name}} here. 8 | 9 | [build-badge]: https://img.shields.io/travis/user/repo/master.png?style=flat-square 10 | [build]: https://travis-ci.org/user/repo 11 | 12 | [npm-badge]: https://img.shields.io/npm/v/npm-package.png?style=flat-square 13 | [npm]: https://www.npmjs.org/package/npm-package 14 | 15 | [coveralls-badge]: https://img.shields.io/coveralls/user/repo/master.png?style=flat-square 16 | [coveralls]: https://coveralls.io/github/user/repo 17 | -------------------------------------------------------------------------------- /templates/web-module/_.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | -------------------------------------------------------------------------------- /templates/web-module/_.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 10 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /templates/web-module/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "1.0.0", 4 | "description": "Describe {{name}} here", 5 | "main": "lib/index.js",{{esModulesPackageConfig}} 6 | "files": [ 7 | "es", 8 | "lib", 9 | "umd" 10 | ], 11 | "scripts": { 12 | "build": "nwb build-web-module", 13 | "clean": "nwb clean-module", 14 | "prepublishOnly": "npm run build", 15 | "test": "nwb test", 16 | "test:coverage": "nwb test --coverage", 17 | "test:watch": "nwb test --server" 18 | }, 19 | "dependencies": { 20 | }, 21 | "devDependencies": { 22 | "nwb": "{{nwbVersion}}" 23 | }, 24 | "author": "", 25 | "homepage": "", 26 | "license": "MIT", 27 | "repository": "" 28 | } 29 | -------------------------------------------------------------------------------- /templates/web-module/nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'web-module' 3 | } 4 | -------------------------------------------------------------------------------- /templates/web-module/src/index.js: -------------------------------------------------------------------------------- 1 | export default 'Welcome to {{name}}' 2 | -------------------------------------------------------------------------------- /templates/web-module/tests/_.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /templates/web-module/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | 3 | import message from 'src/index' 4 | 5 | describe('Module template', () => { 6 | it('displays a welcome message', () => { 7 | expect(message).toContain('Welcome to {{name}}') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /templates/webpack-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/commands/build-test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | import expect from 'expect' 5 | import glob from 'glob' 6 | import rimraf from 'rimraf' 7 | import temp from 'temp' 8 | 9 | import cli from '../../src/cli' 10 | 11 | let stripHashes = (files) => files.map(file => file.replace(/\.\w{8}\./, '.')) 12 | 13 | describe('command: build', function() { 14 | this.timeout(90000) 15 | 16 | let originalCwd 17 | let originalNodeEnv 18 | let tmpDir 19 | 20 | function setUp() { 21 | originalCwd = process.cwd() 22 | originalNodeEnv = process.env.NODE_ENV 23 | delete process.env.NODE_ENV 24 | tmpDir = temp.mkdirSync('nwb-test') 25 | process.chdir(tmpDir) 26 | } 27 | 28 | function tearDown(done) { 29 | process.chdir(originalCwd) 30 | process.env.NODE_ENV = originalNodeEnv 31 | rimraf(tmpDir, done) 32 | } 33 | 34 | describe('building a React app', () => { 35 | let builtHTML 36 | 37 | before(done => { 38 | setUp() 39 | cli(['new', 'react-app', 'test-app'], (err) => { 40 | expect(err).toNotExist('No errors creating a new React app') 41 | process.chdir(path.join(tmpDir, 'test-app')) 42 | cli(['build'], (err) => { 43 | expect(err).toNotExist('No errors building a React app') 44 | builtHTML = fs.readFileSync('dist/index.html', 'utf8') 45 | done(err) 46 | }) 47 | }) 48 | }) 49 | after(tearDown) 50 | 51 | it('creates a build with sourcemaps', () => { 52 | let files = stripHashes((glob.sync('*', {cwd: path.resolve('dist')}))).sort() 53 | expect(files).toEqual([ 54 | 'app.css', 55 | 'app.css.map', 56 | 'app.js', 57 | 'app.js.map', 58 | 'index.html', 59 | 'react.svg', 60 | 'runtime.js', 61 | 'runtime.js.map', 62 | 'vendor.js', 63 | 'vendor.js.map', 64 | ]) 65 | }) 66 | it('injects the Webpack runtime into generated HTML', () => { 67 | expect(builtHTML).toInclude('self.webpackChunk') 68 | }) 69 | it('does not generate a